Overview

In this module, we will learn how to work with structural topic models using the stm package. Structural topic models are useful for gaining insight into the structure of discourse in a particular corpus. For instance, you can use an STM to ask questions like:

In this case, we’ll use STM models to investigate between-party differences in discourse related to President Bill Clinton’s impeachment. Ultimately, Clinton was impeached by the House of Representatives. However, there was a strong partisan split in the vote, such that the majority of Democrats voted against impeachment and the majority of Republicans voted for impeachment.

Accordingly, we’ll use an STM model to ask questions like:

Data Preparation

First, we’ll load the tidy-format version of our data.


dat_tt_words <- read.fst('../data/tdta_clean_house_data_tidy.fst')

Next, we’ll remove stop-words and lemmatize.


dat_tt_words.cln <- dat_tt_words %>%
  anti_join(stop_words) %>% # Drop stop words
  filter(word != 'num') %>% # Drop token that we used to represent numbers 
  mutate(word_lemma = textstem::lemmatize_words(word)) # lemmatize
Joining, by = "word"

Now, we’ll calculate word counts. Here, we’ll count by doc_num, Party, and date in order to preserve these variables; however, what we’re really interested in is the counts of each token for each document. Because all documents have only one value of Party and date, conditioning the count on these variables doesn’t change our calculations.



dat_tt_words.cln <- dat_tt_words.cln %>% 
  count(doc_num, Party, date, word_lemma)

  

Now, we’ll subset our data. Specifically, we will focus on documents generated between September, 1998 and January, 1999. For reference, the key moments in this data, with regard to Clinton’s impeachment hearings, were in October and December of 1999.

dat_tt_words.cln.samp <- dat_tt_words.cln %>%
  filter(date >= as_datetime('1998-09-01') & date <= as_datetime('1999-01-31'))

dat_tt_words.cln.samp %>%
  distinct(Party, doc_num) %>%
  count(Party)
NA

Subsetting on this date range yields about 8,800 documents with roughly even samples for Republicans and Democrats. However, there are only 42 documents associated with Independents. For simplicity, we’ll focus only on Democrats and Republicans.

dat_tt_words.cln.samp <- dat_tt_words.cln.samp %>%
  filter(Party != 'Independent')

Train/Text Split

Finally, for our exploratory analysis, we’ll take a 50% training sample from our subset data.

dat_tt_words.cln.samp.train <- dat_tt_words.cln.samp %>%
  filter(doc_num %!in% doc_ids_test) # Select documents for training
Error in x %in% y : object 'doc_ids_test' not found

Data formating for STM models

Pre-processing for STM models

To fit our STM models, we’ll use the fantastic stm package. While stm has it’s own functions for processing text data, we’ll try to do most of our processing with tidytext, which allows us to maintain consistenty with other use cases.

First, we need to cast our tidy format text data into a so-called sparse document-term matrix. We’ll also take some other pre-processing steps to simplify the modeling process. Specifically, we’ll drop very common and very uncommon words. This can dramatically minimize the parameter space of the model (remember, it has distributions over every word for each topic) and mitigate challenges posed by sparsity.


# Cast to sparse matrix, which is valid for textmineR
train_dtm <- dat_tt_words.cln.samp.train %>%
  cast_sparse(doc_num, word_lemma, value=n)

# User textminor function TermDocFreq to extract term and document frequencies
tf <- TermDocFreq(dtm = train_dtm) %>%
    mutate(doc_prop = doc_freq/n_docs) 


# Exclude words that (1) occur in less than 1% of documents or (2) occur in more than 99% of documents .
words_to_keep <- tf %>%
  filter(doc_prop >= .01 & doc_prop <= .99)

cat(paste('N words: ', nrow(tf), '\nN words after filtering: ', nrow(words_to_keep), sep=''))
N words: 17910
N words after filtering: 1839
# Drop these words from our training data

dat_tt_words.cln.samp.train <- dat_tt_words.cln.samp %>%
  filter(word_lemma %in% words_to_keep$term)

By dropping uncommon and common words, we’ve decreased our vocabular by an order of magnitude. In practice, it’s important to do sensitivity analyses over different thresholds; but for our purposes, we’ll assume that this transformation doesn’t dramatically change the meaning of our documents.

Now that we’ve filtered out infrequent/frequent words, we’ll recast our DTM. We’ll also create a design matrix containing our covariates.


# Cast training data into sparse DTM 
train_sparse <- dat_tt_words.cln.samp.train %>%
  cast_sparse(doc_num, word_lemma, value=n)


# Create a date range from the min/max dates in our training data
date_grid <- tibble(date = seq(min(dat_tt_words.cln.samp.train$date), 
                               max(dat_tt_words.cln.samp.train$date), by='days')) %>%
  mutate(date_int = row_number()) # Associate each date with an integer


# Create design matrix 
train_X <- dat_tt_words.cln.samp.train %>%
  distinct(doc_num, Party, date) %>%
  left_join(date_grid) # Merge our dates with the date grid so that we can represent date as a sequenc of integers
Joining, by = "date"

Working with STM models

Fitting STM models

Now we’re ready to fit our STM models! Because we don’t know the true number of topics, K, we’ll fit a model over a grid of K values ranging from 5 to 60 at intervals of 5. So, in total, we’ll train 12 topic models. This takes quite a while to run, even on a powerful machine. To help minimize training time, I’m using the furrr package to train the models in parallel. However, even in parallel, this code takes a while to run. If you don’t want to run it now, you can load the final object, many_models, from the /models/ directory in our workshop directory.

To fit our STM models, we’ll specify a value of K, the sparse matrix we want to train the model on, our model for topic prevalence, and the data.frame that contains our covariates. Here, we’ll model topic prevelance as a function of Party, a binary factor, and time, which we’ll model with a spline. In practice, you may want to try different functional forms, e.g. perhaps for time. In this data, we do not really have a continuous (or even approximately continuous) measure of time, so it might make more sense to treat day, or week, as a categorical variable.

## This takes quite a while to run!
## To save time, you can just load the `many_models` object, which is saved as `stm_models.RDS` in the /modeles directory.
## 


# Parallel model fitting adapted from https://juliasilge.com/blog/evaluating-stm/

# Uncomment and run to set number of cores to be used for parallel processing
# options(mc.cores = 6)

# Setup env for multiprocessing
plan(multisession, gc = TRUE)

many_models <- data_frame(K = seq(5,60,5)) %>% # Initialize a column of values for K 
  mutate(topic_model = future_map(K,   # map values of K into `stm` in parallel 
                                  ~stm(train_sparse,  # Sparse matrix
                                       K = .,         # placeholder for K
                                       prevalence = ~ Party*s(date_int), # prevalence model
                                       data = train_X, verbose=F)))

#saveRDS(many_models, '../models/stm_models.RDS')
many_models <- readRDS('../models/stm_models.RDS')

Evaluating STM models

Now that we’ve trained our models, we’ll try to pick a specific model based on various measure of model fit/quality. In this case, we’re trying to decide on the optimal number of Topics.

Note: In practice, unless there is a very clear winner, it’s probably a good idea to conduct sensitivity analyses over models with different numbers of topics.

First, we’ll extract a bunch of model fit metrics:


# Adapted from https://juliasilge.com/blog/evaluating-stm/

heldout <- make.heldout(train_sparse) # Here we're setting aside some heldout data that we'll use to evaluate our model. 
                                      # However, really, this data should NOT be taken from our training data!


k_result <- many_models %>%
  mutate(exclusivity = map(topic_model, exclusivity),
         semantic_coherence = map(topic_model, semanticCoherence, train_sparse),
         eval_heldout = map(topic_model, eval.heldout, heldout$missing),
         residual = map(topic_model, checkResiduals, train_sparse),
         bound =  map_dbl(topic_model, function(x) max(x$convergence$bound)),
         lfact = map_dbl(topic_model, function(x) lfactorial(x$settings$dim$K)),
         lbound = bound + lfact,
         iterations = map_dbl(topic_model, function(x) length(x$convergence$bound)))

Now, let’s plot some of these metrics as a function of K:

k_result %>%
  transmute(K,
            `Lower bound` = lbound,
            Residuals = map_dbl(residual, "dispersion"),
            `Semantic coherence` = map_dbl(semantic_coherence, mean),
            `Held-out likelihood` = map_dbl(eval_heldout, "expected.heldout")) %>%
  gather(Metric, Value, -K) %>%
  ggplot(aes(K, Value, color = Metric)) +
  geom_line(size = 1.5, alpha = 0.7, show.legend = FALSE) +
  facet_wrap(~Metric, scales = "free_y") +
  labs(x = "K (number of topics)",
       y = NULL,
       title = "Model diagnostics by number of topics",
       subtitle = "These diagnostics indicate that a good number of topics would be around 50 or 60")

Ulimately, we want to pick a model that maximizes semantic coherence, roughly the likelihood that high probability words in a given topic co-occur in a high-probability document for that topic. However, at the same time, we want to minimize our residuals and maximize the held-out likelihood and the marginal probability of the data given the model, which is referred to, here, as the “lower bound”. Unfortunately, semantic coherence and these other metrics usually move in opposite directions.

In this case, based on our residuals, held-out likelihood, and lower bound plots, 50 <= K >= 60 is a good range. It also looks like these models are tied (at the lowest) levels of semantic coherence.

Coherence vs Exclusivity

Another important diagnostic is exclusivity, which represents the degree to which the highest probability words in a topic are exclusive or unique to that topic. This is a valuable complement to coherence, because you could maximize coherence by assigning the words with the highest marginal empirical probabilities to all topics (e.g. all topics place the most density on “the”, “and”, and “is”, for example). In this case, we could tell that this is a “bad” model by looking at the exclusivity scores for the model’s topics, which would be very low because all topics share their high probability words.


K_means <- k_result %>%
  select(K, exclusivity, semantic_coherence) %>%
  filter(K %in% c(40, 45, 50, 55, 60)) %>%
  unnest(cols = c(exclusivity, semantic_coherence)) %>%
  mutate(K = as.factor(K)) %>%
  group_by(K) %>%
  summarize_all(.funs = c(mean, sd))



k_result %>%
  select(K, exclusivity, semantic_coherence) %>%
  filter(K %in% c(40, 45, 50, 55, 60)) %>%
  unnest(cols = c(exclusivity, semantic_coherence)) %>%
  mutate(K = as.factor(K)) %>%
  ggplot(aes(semantic_coherence, exclusivity, color = K)) +
  geom_point(size = 2, alpha = 0.7) +
  labs(x = "Semantic coherence",
       y = "Exclusivity",
       title = "Comparing exclusivity and semantic coherence",
       subtitle = "Models with fewer topics have higher semantic coherence for more topics, but lower exclusivity") +
  #facet_wrap(K~.) +
  geom_hline(data=K_means, aes(yintercept=exclusivity_fn1, color=K)) + 
  geom_vline(data=K_means, aes(xintercept=semantic_coherence_fn1, color=K)) 

NA
NA

It looks like the model with 60 topics has the highest average exclusivity and the 3rd highest average coherence. However, this is a bit hard to see, so we can also look at the point estimates.

K_means %>%
  rename(mean_exclusivity = exclusivity_fn1,
         mean_semantic_coherence = semantic_coherence_fn1,
         sd_exclusivity = exclusivity_fn2,
         sd_semantic_coherence = semantic_coherence_fn2) %>%
  select(K, mean_exclusivity, sd_exclusivity, mean_semantic_coherence, sd_semantic_coherence) %>%
  mutate_if(is.numeric, round, digits=2)

Interestingly, it looks like exclusivity is the same for K = 50, 55, and 60, though the SD is a little lower for K = 55 and 60. In contrast, the model with K = 60 actually has the 4th lowest semantic coherence.

Choosing a model

Ultimately, our model residuals and marginal fit statistics indicate that the model with 60 topics is the best. However, comparisons semantic coherence and exclusivity suggest that other models could be just as good, depending on how you define model success.

When it’s hard to identify a clear winner, you should almost always conduct sensitivity analyses across multiple models! If they all lead you to the same conclusion, then perhaps that conclusion warrants greater trust. However, if they all lead you to different conclusions, then you probably shouldn’t trust any of them!

However, for our purposes, we’ll choose the model with K=60 and proceed with our analyses.

stm_model.1.train <- k_result %>% 
  filter(K == 60) %>% 
  pull(topic_model) %>% 
  .[[1]]

stm_model.1.train
A topic model with 60 topics, 8930 documents and a 1839 word dictionary.

Exploring STM models

Now that we’ve selected a topic model, we can begin to answer some of our questions.

What topics are relevant to our corpus?

To take a high-level glance at the topics estimated by our model, we can use the stm plot function with type='summary. This plot orders topics by the marginal proportion (e.g. likelihood of occurance) and shows the top words associated with each topic.

plot(stm_model.1.train, type = "summary", xlim = c(0, .3), text.cex=1.5)

By default, the top words are defined as the words with the highest probability. However, we can also change this so that the top words are the words with the highest exclusivity score.


plot(stm_model.1.train, type = "summary", xlim = c(0, .3), text.cex=1.5, n=5, labeltype='frex')

Inter-topic correlations

In contrast to vanilla LDA models, STM models estimate a covariance matrix for the distribution of topics. We can use this covariance matrix to visualize associations among topics.

library(igraph)
cormat <- topicCorr(stm_model.1.train)
set.seed(123)
plot(cormat, niter=5000, repulserad=60^4*10, 
     edge.arrow.size=0.5, 
     vertex.label.cex=0.75, 
     vertex.label.family="Helvetica",
     vertex.label.font=2,
     vertex.shape="circle", 
     vertex.size=2, 
     vertex.label.color="black", 
     edge.width=0.5)

This is hard to read, so let’s try a different kind of plot:


library(reshape2)

melted_cormat <- melt(cormat$cor)

ggplot(data = melted_cormat, aes(x=Var1, y=Var2, fill=value)) + 
geom_tile()

This is still hard to read! Because I’m primarily interested in associations with Topic 25, I’ll just plot the correlations with that topic.

library(plotly) 

p <- melted_cormat %>%
  filter(Var1 == 25 & Var2 != 25) %>%
  ggplot(aes(x = Var2, y = value)) + geom_text(aes(label=Var2)) + 
  ylab('Correlation') +
  xlab('Topic') +
  ggtitle('Correlations of Topic 25 with other topics')
  
ggplotly(p)

It looks like topic 25 is most strongly related to 35, 57, 53, so let’s take a closer look at those topics.

Evaluating topic content

Out of 60 topics, the one’s that seem most relevant to our questions about impeachment are 25, 35, 57, and 53. But, what do these topics mean? To get a better idea of their subjective meaning, we can look again at their top words.


plot(stm_model.1.train, type = "summary", xlim = c(0, .3), n=5, labeltype='prob', 
     topics = c(25, 35, 57, 53))

Examining relevant documents

Lookin at the top words associated with a topic is a good way to get an idea of what the topic represents. However, there is another crucial source of information: the documents most strongly associated with the topic. When trying to summarize topics, you should always look at the top words and the top documents.

We can do this using the findThoughts function, which prints the text of the documents most strongly associated with a particular topic. However, to do this, we first need to make our texts accessible. For simplicity, I’ll just examine the first 500 characters in each relevant document.


texts = dat_tt_words %>% # This tidy data.frame contains the original words (i.e. before we lemmatized)
  filter(doc_num %in% unique(dat_tt_words.cln.samp.train$doc_num)) %>% # Keep only the docs in our training data
  group_by(doc_num) %>% 
  summarize(text = str_sub(paste0(word, collapse=' '), 1,500)) %>% # Collapse the rows of words into a single cell
  ungroup()

findThoughts(stm_model.1.train, texts = texts$text, topics = c(25), n=3)

 Topic 25: 
     like most americans i believe the president's behavior was irresponsible inappropriate and deeply disappointing but like most americans i have concluded that his actions do not rise to standard of impeachment established by the framers of our constitution make no mistake the president is not above the law he can be sued in criminal or civil proceedings for his actions in this matter when he leaves office but as members of congress we have a unique responsibility and must adhere to the standards 
    equal justice means two things first every citizen including the least powerful like the plaintiff in the first civil case in which president clinton perjured himself has a right to demand truthful testimony under oath even when the defendant is the president secondly equal justice requires adherence to the rule of law by all americans including the most powerful further equal justice requires accountability by those who have committed perjury in this case accountability for perjury is provided 
    today we are faced with strong evidence that the president lied after swearing an oath to tell the truth we have only one legitimate remedy in front of us impeachment so with great remorse i will vote in favor of impeachment some people have said that impeaching the president is an extremist or radical position to those people i must ask is holding the president accountable for his actions extremist is expecting the president to tell the truth radical i submit to this house that the rational pos

findThoughts(stm_model.1.train, texts = texts$text, topics = c(35), n=3)

 Topic 35: 
     may i just intercede with a thought i have a couple of other members here who have been waiting they want to speak i would hope and i am sure that we all would agree that we perhaps could allow these members to speak but perhaps we could be brief and then conclude the day's business
    i was not planning to address the body at this time but a colleague just impugned the moderates who have decided not to vote their way as if we are somehow being pressured i would challenge anyone on this floor to name the moderates who have come to you and said we have been pressured i for one and my colleagues i have spoken to have said this is a vote of conscience and respect our vote of conscience as much as you are asking us to respect yours i think it is outrageous that my colleagues on th
    i yield myself such time as i may consume i simply want to say that i think this has been a good healthy discussion i appreciate the various points of view that have been presented we all clearly wants to clean up the environment that is not the issue here i commend the gentleman from california mr bilbray for coming forth i think it has been terrific that we have heard this debate because clearly it is a more complex issue than what initially meets the eye there are many facets to this discussi
findThoughts(stm_model.1.train, texts = texts$text, topics = c(57), n=3)

 Topic 57: 
     republicans in congress have a message to the president don't shut down the government republicans have been working with the administration since last spring to avoid a government shutdown i think we all agree it is not in the national interest to shut down the government but how tragic it would be if the president were to force such a shutdown to divert attention from other matters or to use it for political purposes as we head into the mid term elections republicans are willing to reach an ho
    republicans in congress have a message to the president do not shut the government down republicans have been working with this administration since last spring last spring to avoid a government shutdown this year i think we would agree that it is not in the national interests to shut down the government how tragic it would be if the president were to force a shutdown for political reasons republicans are willing to reach a compromise with the white house on our remaining differences just as we 
    we are now in the midst of another battle over the budget the president remains steadfast in his unwillingness to meet and try to find a way to work out a compromise so we can keep the government running the president expressed dismay that all num appropriation bills had not been passed by the congress and signed into law yet since num when the democrats controlled congress the congress failed entirely to pass all num appropriation bills num times that is right at least num times a democrat cong
findThoughts(stm_model.1.train, texts = texts$text, topics = c(53), n=3)

 Topic 53: 
     yes i would and i would like to add that the attorney general has appointed independent counsels for some of the periphery of this administration but whenever it gets close to the oval office or people close to the oval office there is a reluctance to go ahead and appoint an independent counsel instead of doing this piecemeal as has been the case by the justice department there should be one independent counsel to look at the whole campaign finance scandal the money that has come from all over t
    for over num years now despite overwhelming evidence the attorney general of the united states has refused to follow the law and the recommendations of her fbi director and the chief campaign finance prosecutor to appoint an independent counsel in the campaign finance scandal she has politicized the office over which she has control the justice department of the united states reports about disarray in this investigation at the justice department abound after num years of this investigation key p
    well the cia has finally admitted it and the new york times finally covered it the times ran the devastating story on saturday with the headline cia said to ignore charges of contra drug dealing in nums in a remarkable reversal by the new york times the paper reported that the cia knew about contra drug dealing and they covered it up the cia let it go on for years during the height of their campaign against the sandinista government among other revelations in the article were that the cia's insp
  • Topic 25:
  • Topic 35:
  • Topic 57:
  • Topic 53:

Hypothesis testing with STMs

Clearly, Topic 25 is the most strongly related to impeachment. So, let’s use the topic prevalence component of our model to estimate the distribution of Topic 25 over time and by party.

To do this, we’ll use stm’s estimateEffect function. Then, we’ll use tidystm’s function extract.estimateEffect to extract a tidy dataframe of the estimated effects.

Importantly, the estimateEffect function uses documents as units and the topic proportion as the outcome. Thus, our effects estimates reflect expected changes in the topic proportion for a given document, conditional on our covariates.

To get a better idea of what these effects imply, let’s visualize them.


label_dat <- data.frame(date = as.numeric(as_datetime('1998-10-08')), label='test', estimate=.2)
                        
effs %>%
  left_join(date_grid, by = c('covariate.value'='date_int')) %>%
  ggplot(aes(x = date, y = estimate)) + 
  geom_ribbon(aes(ymin=ci.lower, ymax=ci.upper, fill=moderator.value), alpha=.25) + 
  geom_line(aes(color=moderator.value)) +
  theme_apa() + 
  ylab('Topic Proportion') +
  xlab('Date') +
  geom_vline(xintercept=as.numeric(as_datetime('1998-10-08')), linetype=2) + 
  geom_vline(xintercept=as.numeric(as_datetime('1998-12-19')), linetype=2) +
  geom_label(aes(x = as_datetime('1998-10-08'), y=.32, label = "Impeachment Initiated")) +
  geom_label(aes(x = as_datetime('1998-12-19'), y=.32, label = "Impeachment Vote")) + 
  ggtitle('Estimated topic proportions by date and party') + 
  facet_wrap(topic~., ncol=1)

Now, let’s look specifically at the effects for the two days relevant to impeachment, the day the impeachment was initiated and the day it was voted on.

Investigating Topic Content

It seems clear that Democrats’ floor speeches were more relevant to Topic 25 than Republicans.

However, what if they speak about these topics in different ways? To investigate this hypothesis, we can estimate a new model that models topic content as a function of Party.

stm_model.2.train <- stm(train_sparse,
            K = 60,
            prevalence = ~ Party*s(date_int), # prevalence model
            content = ~ Party,
            data = train_X,
            verbose=F)

saveRDS(stm_model.2.train, '../models/stm_model_2_train.RDS')
stm_model.2.train <- readRDS('../models/stm_model_2_train.RDS')

Now, we’ll visualize between-party differences in topic content using the stm plot function with type='perspectives'.

plot(stm_model.2.train, type = "perspectives", topics = 25, n = 100)

In this figure, a word’s size indicates its association with the topic. Further, it’s position on the X-axis indicates it’s differential association with the specified levels of the covariate.

Validation with STM models

At this point, we’ve fit a bunch of STM models and picked one for further exploration. Based on what we observed, it seems that Democrats spoke more about impeachment on the day of the vote, but also that their discussions of impeachment focused more on “process”, whereas Republicans’ discussions of impeachment focused more explicitly on the president and words like “justice”, “truth”, and “lie”.

Given our understanding of the data, this makes sense! However, are these findings robust? To address this question, we will fit a new model on our held-out confirmation data. Ideally, we’d like to see the same topic structure and come to the same conclusions. However, if our conclusions deviate from our current expectations, we may need to accept the possibility that our conclusions are not reliable.

# Create design matrix 
test_X <- dat_tt_words.cln.samp.test %>%
  distinct(doc_num, Party, date) %>%
  left_join(date_grid) # Merge our dates with the date grid so that we can represent date as a sequenc of integers
Joining, by = "date"

stm_model.2.test <- stm(test_sparse,
            K = 60,
            prevalence = ~ Party*s(date_int), # prevalence model
            content = ~ Party,
            data = test_X,
            verbose=F)

First, let’s look at the top words for each topic.


plot(stm_model.2.test, type = "summary", xlim = c(0, .3), text.cex=1.5, n=5)

Uh oh, our nice “impeach” topic didn’t show up!

Extracting the beta matrix

One way to look for relevant topics is to identify topics that place the highest probability on relevant keywords, such as “impeachment”. To do this, we’ll extract the beta matrix from our model and identify the topics that place the highest probability on “impeachment”

Interesting, it looks like topic 50 has, by far, the greatest probability density over ‘impeachment’. Let’s look at this topic, along with 15, 46, and 35


plot(stm_model.2.test, type = "summary", xlim = c(0, .3), n=5, topics=c(50,15,46,35))

Okay, if you squint, Topic 50 could definitely be related to impeachment. Also, “Paula” in topic 46 is probably related to Paula Jones, the person who filed a sexual harassment lawsuit filed against Clinton.

Examining relevant documents

Let’s dig a bit deeper by looking at the relevant documents for each topic:


texts = dat_tt_words %>% # This tidy data.frame contains the original words (i.e. before we lemmatized)
  filter(doc_num %in% unique(dat_tt_words.cln.samp.test$doc_num)) %>% # Keep only the docs in our training data
  group_by(doc_num) %>% 
  summarize(text = str_sub(paste0(word, collapse=' '), 1,500)) %>% # Collapse the rows of words into a single cell
  ungroup()

findThoughts(stm_model.2.test, texts = texts$text, topics = c(50), n=3)

 Topic 50: 
     num years ago i walked into this chamber with awe as the son of an immigrant i was raised to believe in the majesty of our democracy and this is the citadel of that democracy today we are on the verge of weakening our democracy by abusing the most extraordinary tool our constitution affords us most constitutional scholars and most of the american people simply do not believe that the president's offenses as bad as they are rise to the level of impeachment yet we are about to set a dangerous prec
    like most americans i believe the president's behavior was irresponsible inappropriate and deeply disappointing but like most americans i have concluded that his actions do not rise to standard of impeachment established by the framers of our constitution make no mistake the president is not above the law he can be sued in criminal or civil proceedings for his actions in this matter when he leaves office but as members of congress we have a unique responsibility and must adhere to the standards 
    i have not to this point formally announced how i would vote on these four articles of impeachment in reaching my decision i have weighed not only my constitutional duty and this president's fate but i have weighed what vote is the right one for the country at this time i have concluded that this president can and should continue in office for the remainder of his elected term in making my decision i have looked carefully at the words of our framers particularly the founder of my hometown of pat

This topic certainly seems relevant to impeachment; these (very few!) documents also suggest that maybe this topic is associated with not supporting impeachment.


findThoughts(stm_model.2.test, texts = texts$text, topics = c(35), n=3)

 Topic 35: 
     our courts of law and our legal system are the bedrock of our democracy and of our system of individual rights lying under oath in a legal proceeding and obstruction of justice undermine the rights of all citizens who must rely upon our courts to protect their rights if lying under oath in our courts and obstruction are ignored or they are classified as merely minor offenses then we have jeopardized the rights of everyone who seeks redress in our courts lying under oath is an ancient crime of gr
    with a commitment to the principles of the rule of law which makes this country the beacon of hope throughout the world i cast my vote in favor of the four counts of impeachment of the conduct of the president of the united states as a representative in congress i can do no less in fulfilling my responsibility to the constitution and to all who have preceded me in defending the constitution from erosions of the rule of law each of the impeachment counts concerns the public conduct of the preside
    with a commitment to the principles of the rule of law which makes this country the beacon of hope throughout the world i cast my vote in favor of the resolution to undertake an impeachment inquiry of the conduct of the president of the united states as a representative in congress i can do no less in fulfilling my trust responsibility to the constitution and to all who have preceded me in defending the constitution from erosions of the rule of law the impeachment inquiry is necessary to determi

This topic also seems to be about impeachment; however, it seems that the top documents express positions for impeachment.

findThoughts(stm_model.2.test, texts = texts$text, topics = c(46), n=3)

 Topic 46: 
     today the united states house of representatives begins debate on articles of impeachment of president william jefferson clinton this is the first time in over num years that the house of representatives has performed its solemn duty of determining whether a sitting president should be impeached article ii section num of our constitution reads the president vice president and all civil officers of the united states shall be removed from office on impeachment for and conviction of treason bribery
    i rise in support of the articles of impeachment private sexual relations between consenting adults should be just that private if president clinton had simply been revealed to have had an extra marital affair the u.s house of representatives would not be considering articles of impeachment unfortunately the president's troubles arise from a number of actions quite different from private consensual sexual encounters before the president even knew monica lewinsky he was the defendant in a civil l
    i quote do you solemnly swear in the testimony you are about to give that it will be the truth the whole truth and nothing but the truth so help you god that is the oath president clinton took before his august numth testimony of this year the president answered i do and despite repeated attempts by deputy independent counsel sol wisenberg to warn him of the consequences of providing false or misleading testimony the president went on to make perjurious statements pertaining to his relationship 

This also appears to be about impeachment, though with greater focus on lying and proceedings.

findThoughts(stm_model.2.test, texts = texts$text, topics = c(15), n=3)

 Topic 15: 
     by direction of the house republican conference i call up a privileged resolution a href billnumth congresshouse resolutionnum h res num a and ask for its immediate consideration the clerk read the resolution as follows a href billnumth congresshouse resolutionnum h res num a resolved that the rules of the house of representatives of the one hundred fifth congress including applicable provisions of law or concurrent resolution that constituted rules of the house at the end of the one hundred fif
    most respectfully i thank you for recognizing me and permitting me to act expeditiously in a matter that i wish to bring to the attention of the house pursuant to rule ix i hereby give notice of my intention to offer a resolution as a question of the privilege of the house the form of my resolution is as follows and i shall try to be as expeditious as possible impeaching kenneth w starr an independent counsel of the united states appointed pursuant to num united states code section num b of high
    at the conclusion of this debate i will offer a motion to recommit the resolution offered by the gentleman from illinois to the committee on the judiciary with the instruction that the committee immediately report to the house the resolution in the form of our democratic alternative while we would have preferred that democrats have a normal opportunity to present our resolution as a amendment the procedure being used by the house today does not make a democratic amendment in order the motion to 

This appears to be less relevant to impeachment (as we have been thinking about it) and more relevant to proceedings.

  • 35:
  • 46:
  • 35:
  • 15:

Hypothesis testing on validation set

While these topics are different from what we observed in our training data, they still seem relevant. Let’s go ahead and visualize the effects of our covariates:

library(tidystm)

prep.test = estimateEffect(c(50, 46, 35) ~ Party*s(date_int), stm_model.2.test, meta=test_X)

  
effs.test <- purrr::map(c('Democratic', 'Republican'), # Levels of moderator
                   ~extract.estimateEffect(prep.test, # effects estimate object
                                           "date_int", # The IV we want to look at
                                           model = stm_model.2.test, # Our STM model
                                           moderator='Party', # Moderator 
                                           moderator.value = .)) %>% # Moderator levels, which we specify via `map`
  do.call('rbind', .) # Here, we rbind the mapped lists, which yields a single DF

label_dat <- data.frame(date = as.numeric(as_datetime('1998-10-08')), label='test', estimate=.2)
                        
effs.test %>%
  left_join(date_grid, by = c('covariate.value'='date_int')) %>%
  mutate(topic = recode(topic, `50` = 'Oppose impeachment', `46` = 'Lying', `35` = 'Support impeachment' )) %>%
  ggplot(aes(x = date, y = estimate)) + 
  geom_ribbon(aes(ymin=ci.lower, ymax=ci.upper, fill=moderator.value), alpha=.25) + 
  geom_line(aes(color=moderator.value)) +
  theme_apa() + 
  ylab('Topic Proportion') +
  xlab('Date') +
  geom_vline(xintercept=as.numeric(as_datetime('1998-10-08')), linetype=2) + 
  geom_vline(xintercept=as.numeric(as_datetime('1998-12-19')), linetype=2) +
  geom_label(aes(x = as_datetime('1998-10-08'), y=.32, label = "Impeachment Initiated")) +
  geom_label(aes(x = as_datetime('1998-12-19'), y=.32, label = "Impeachment Vote")) + 
  ggtitle('Estimated topic proportions by date and party') + 
  facet_wrap(topic~., ncol=1)

Investigating Topic Content on validation set

Finally, we can take a look at party differences in topic content. First, let’s look at Topic 50, the topic we’re thinking of as associated with opposition to impeachment.

plot(stm_model.2.test, type = "perspectives", topics = 50, n = 100)

This looks somewhat similar to the perspectives plot of Topic 25 in our training data. However, there are some key differences, too.

Now let’s look at Topic 35, the topic we’re thinking of as associated with supporting impeachment:

plot(stm_model.2.test, type = "perspectives", topics = 35, n = 100)

Interesting, it looks like, at least sometimes, Democrats might be talking about other issues (e.g. not Clinton’s impeachment) in the context of this topic.

Finally, let’s look at Topic 46, the topic that seems to be a little more generally about “lying”.

plot(stm_model.2.test, type = "perspectives", topics = 46, n = 100)

Here, we can see that Republican’s are more likely to mention Lewinsky and Jones, key people in the case against Clinton. Their use of this topic also places more density on words like “lie” and “perjury”.

Conclusions

LS0tCnRpdGxlOiAiVGhlb3J5IERyaXZlbiBUZXh0IEFuYWx5c2lzIFdvcmtzaG9wIgpzdWJ0aXRsZTogIlRvcGljIE1vZGVsc1xuXG5TUFNQIDIwMjAiCmF1dGhvcjogCiAgbmFtZTogIkpvZSBIb292ZXIgJiBCcmVuZGFuIEtlbm5lZHkiCiAgZW1haWw6ICJqb3NlcGguaG9vdmVyQGtlbGxvZ2cubm9ydGh3ZXN0ZXJuLmVkdVxuXG5idGtlbm5lZEB1c2MuZWR1IgpvdXRwdXQ6CiAgaHRtbF9ub3RlYm9vazoKICAgIHRvYzogeWVzCi0tLQoKCmBgYHtyLCBlY2hvPUYsIG1lc3NhZ2U9Riwgd2FybmluZz1GfQojIERlZmluZSBjaHVuayBvcHRpb25zCmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvPVQsIG1lc3NhZ2U9Riwgd2FybmluZz1GKQpgYGAKCmBgYHtyLCBtZXNzYWdlPUYsICBlY2hvPUZ9CiMgTG9hZCBwYWNrYWdlcyAKbGlicmFyeShwYWNtYW4pCnBfbG9hZChyZWFkciwgZHBseXIsIHRpZHlyLCBnZ3Bsb3QyLCBwbXIsIGp0b29scywga25pdHIsIHJlc2hhcGUyLCBqc29ubGl0ZSwgCiAgICAgICBsdWJyaWRhdGUsIHN0cmluZ3IsIHRpZHl0ZXh0LCBmc3QsIHRleHRzdGVtLCB0bSwgcXVhbnRlZGEsIHRvcGljbW9kZWxzLCB0ZXh0bWluZVIsCiAgICAgICBzdG0sIGZ1cnJyKQoKIyBJZiBlcnJvciBvbiBpbnN0YWxsIHRvcGljIG1vZGVscywgdGhpcyBwb3N0IG1pZ2h0IGhlbHA6CiMgaHR0cHM6Ly9zdGFja292ZXJmbG93LmNvbS9xdWVzdGlvbnMvMjU3NTkwMDcvZXJyb3ItaW5zdGFsbGluZy10b3BpY21vZGVscy1wYWNrYWdlLW5vbi16ZXJvLWV4aXQtc3RhdHVzLXVidW50dQoKJyUhaW4lJyA8LSBmdW5jdGlvbih4LHkpISgnJWluJScoeCx5KSkKCmBgYAoKIyBPdmVydmlldyAKCkluIHRoaXMgbW9kdWxlLCB3ZSB3aWxsIGxlYXJuIGhvdyB0byB3b3JrIHdpdGggc3RydWN0dXJhbCB0b3BpYyBtb2RlbHMgdXNpbmcgdGhlIGBzdG1gIHBhY2thZ2UuIFN0cnVjdHVyYWwgdG9waWMgbW9kZWxzIGFyZSB1c2VmdWwgZm9yIGdhaW5pbmcgaW5zaWdodCBpbnRvIHRoZSBzdHJ1Y3R1cmUgb2YgZGlzY291cnNlIGluIGEgcGFydGljdWxhciBjb3JwdXMuIEZvciBpbnN0YW5jZSwgeW91IGNhbiB1c2UgYW4gU1RNIHRvIGFzayBxdWVzdGlvbnMgbGlrZToKCiogV2hhdCB0b3BpY3MgYXJlIHJlbGV2YW50IGluIGEgY29ycHVzCiogRG9lcyB0aGUgZGlzdHJpYnV0aW9uIG9mIHRvcGljcyBjaGFuZ2UgZGVwZW5kaW5nIG9uIGtleSBjb3ZhcmlhdGVzIChlLmcuIGV4cGVyaW1lbnRhbCBjb25kaXRpb24sIHRoZSBwb2xpdGljYWwgYWZmaWxpYXRpb24gb2YgdGhlIHNwZWFrZXIsIGV0YykKKiBEb2VzIHRoZSBjb250ZW50IG9mIGEgdG9waWMgKGkuZS4gaXQncyBkaXN0cmlidXRpb24gb3ZlciB3b3JkcykgY2hhbmdlIGRlcGVuZGluZyBvbiBrZXkgY292YXJpYXRlcwoKSW4gdGhpcyBjYXNlLCB3ZSdsbCB1c2UgU1RNIG1vZGVscyB0byBpbnZlc3RpZ2F0ZSBiZXR3ZWVuLXBhcnR5IGRpZmZlcmVuY2VzIGluIGRpc2NvdXJzZSByZWxhdGVkIHRvIFByZXNpZGVudCBCaWxsIENsaW50b24ncyBpbXBlYWNobWVudC4gVWx0aW1hdGVseSwgQ2xpbnRvbiB3YXMgaW1wZWFjaGVkIGJ5IHRoZSBIb3VzZSBvZiBSZXByZXNlbnRhdGl2ZXMuIEhvd2V2ZXIsIHRoZXJlIHdhcyBhIHN0cm9uZyBwYXJ0aXNhbiBzcGxpdCBpbiB0aGUgdm90ZSwgc3VjaCB0aGF0IHRoZSBtYWpvcml0eSBvZiBEZW1vY3JhdHMgdm90ZWQgKmFnYWluc3QqIGltcGVhY2htZW50IGFuZCB0aGUgbWFqb3JpdHkgb2YgUmVwdWJsaWNhbnMgdm90ZWQgKmZvciogaW1wZWFjaG1lbnQuIAoKQWNjb3JkaW5nbHksIHdlJ2xsIHVzZSBhbiBTVE0gbW9kZWwgdG8gYXNrIHF1ZXN0aW9ucyBsaWtlOgoKKiBXaGVuIGRpZCBkb2N1bWVudHMgKGkuZS4gc3BlZWNoZXMpIG1vc3QgcmVsZXZhbnQgdG8gaW1wZWFjaG1lbnQgb2NjdXI/CiogV2hpY2ggcGFydHkgcHJvZHVjZWQgbW9yZSBkb2N1bWVudHMgcmVsYXRlZCB0byBpbXBlYWNobWVudD8KKiBXaGF0LCBpZiBhbnksIGJldHdlZW4gcGFydHkgZGlmZmVyZW5jZXMgYXJlIHRoZXJlIGluIGRpc2NvdXJzZSBhYm91dCBpbXBlYWNobWVudD8KCgojIERhdGEgUHJlcGFyYXRpb24KCkZpcnN0LCB3ZSdsbCBsb2FkIHRoZSB0aWR5LWZvcm1hdCB2ZXJzaW9uIG9mIG91ciBkYXRhLgpgYGB7cn0KCmRhdF90dF93b3JkcyA8LSByZWFkUkRTKCcuLi9kYXRhL3RkdGFfY2xlYW5faG91c2VfZGF0YV90aWR5LlJEUycpCgpgYGAKCgpOZXh0LCB3ZSdsbCByZW1vdmUgc3RvcC13b3JkcyBhbmQgbGVtbWF0aXplLgoKYGBge3J9CgpkYXRfdHRfd29yZHMuY2xuIDwtIGRhdF90dF93b3JkcyAlPiUKICBhbnRpX2pvaW4oc3RvcF93b3JkcykgJT4lICMgRHJvcCBzdG9wIHdvcmRzCiAgZmlsdGVyKHdvcmQgIT0gJ251bScpICU+JSAjIERyb3AgdG9rZW4gdGhhdCB3ZSB1c2VkIHRvIHJlcHJlc2VudCBudW1iZXJzIAogIG11dGF0ZSh3b3JkX2xlbW1hID0gdGV4dHN0ZW06OmxlbW1hdGl6ZV93b3Jkcyh3b3JkKSkgIyBsZW1tYXRpemUKCmBgYAoKCk5vdywgd2UnbGwgY2FsY3VsYXRlIHdvcmQgY291bnRzLiBIZXJlLCB3ZSdsbCBjb3VudCBieSBkb2NfbnVtLCBQYXJ0eSwgYW5kIGRhdGUgaW4gb3JkZXIgdG8gcHJlc2VydmUgdGhlc2UgdmFyaWFibGVzOyBob3dldmVyLCB3aGF0IHdlJ3JlIHJlYWxseSBpbnRlcmVzdGVkIGluIGlzIHRoZSBjb3VudHMgb2YgZWFjaCB0b2tlbiBmb3IgZWFjaCBkb2N1bWVudC4gQmVjYXVzZSBhbGwgZG9jdW1lbnRzIGhhdmUgb25seSBvbmUgdmFsdWUgb2YgUGFydHkgYW5kIGRhdGUsIGNvbmRpdGlvbmluZyB0aGUgY291bnQgb24gdGhlc2UgdmFyaWFibGVzIGRvZXNuJ3QgY2hhbmdlIG91ciBjYWxjdWxhdGlvbnMuIAoKYGBge3J9CgoKZGF0X3R0X3dvcmRzLmNsbiA8LSBkYXRfdHRfd29yZHMuY2xuICU+JSAKICBjb3VudChkb2NfbnVtLCBQYXJ0eSwgZGF0ZSwgd29yZF9sZW1tYSkKCiAgCmBgYAoKTm93LCB3ZSdsbCBzdWJzZXQgb3VyIGRhdGEuIFNwZWNpZmljYWxseSwgd2Ugd2lsbCBmb2N1cyBvbiBkb2N1bWVudHMgZ2VuZXJhdGVkIGJldHdlZW4gU2VwdGVtYmVyLCAxOTk4IGFuZCBKYW51YXJ5LCAxOTk5LiBGb3IgcmVmZXJlbmNlLCB0aGUga2V5IG1vbWVudHMgaW4gdGhpcyBkYXRhLCB3aXRoIHJlZ2FyZCB0byBDbGludG9uJ3MgaW1wZWFjaG1lbnQgaGVhcmluZ3MsIHdlcmUgaW4gT2N0b2JlciBhbmQgRGVjZW1iZXIgb2YgMTk5OS4KCgpgYGB7cn0KZGF0X3R0X3dvcmRzLmNsbi5zYW1wIDwtIGRhdF90dF93b3Jkcy5jbG4gJT4lCiAgZmlsdGVyKGRhdGUgPj0gYXNfZGF0ZXRpbWUoJzE5OTgtMDktMDEnKSAmIGRhdGUgPD0gYXNfZGF0ZXRpbWUoJzE5OTktMDEtMzEnKSkKCmRhdF90dF93b3Jkcy5jbG4uc2FtcCAlPiUKICBkaXN0aW5jdChQYXJ0eSwgZG9jX251bSkgJT4lCiAgY291bnQoUGFydHkpCgpgYGAKClN1YnNldHRpbmcgb24gdGhpcyBkYXRlIHJhbmdlIHlpZWxkcyBhYm91dCA4LDgwMCBkb2N1bWVudHMgd2l0aCByb3VnaGx5IGV2ZW4gc2FtcGxlcyBmb3IgUmVwdWJsaWNhbnMgYW5kIERlbW9jcmF0cy4gSG93ZXZlciwgdGhlcmUgYXJlIG9ubHkgNDIgZG9jdW1lbnRzIGFzc29jaWF0ZWQgd2l0aCBJbmRlcGVuZGVudHMuIEZvciBzaW1wbGljaXR5LCB3ZSdsbCBmb2N1cyBvbmx5IG9uIERlbW9jcmF0cyBhbmQgUmVwdWJsaWNhbnMuIAoKCmBgYHtyfQpkYXRfdHRfd29yZHMuY2xuLnNhbXAgPC0gZGF0X3R0X3dvcmRzLmNsbi5zYW1wICU+JQogIGZpbHRlcihQYXJ0eSAhPSAnSW5kZXBlbmRlbnQnKQoKYGBgCgoKIyMgVHJhaW4vVGV4dCBTcGxpdCAKCkZpbmFsbHksIGZvciBvdXIgZXhwbG9yYXRvcnkgYW5hbHlzaXMsIHdlJ2xsIHRha2UgYSA1MCUgdHJhaW5pbmcgc2FtcGxlIGZyb20gb3VyIHN1YnNldCBkYXRhLgoKCmBgYHtyfQpkb2NfaWRzIDwtIHVuaXF1ZShkYXRfdHRfd29yZHMuY2xuLnNhbXAkZG9jX251bSkgIyBHZXQgZG9jdW1lbnQgSURzCgpuX2RvY3MgPSAuNTAgKiBsZW5ndGgoZG9jX2lkcykgIyBDYWxjdWxhdGUgbnVtYmVyIG9mIGRvY3VtZW50cyB0byBzYW1wbGUKCnNldC5zZWVkKDEyMzEpICMgc2V0IHNlZWQgZm9yIHJlcHJvZHVjaWJpbGl0eQoKZG9jX2lkc190ZXN0ID0gc2FtcGxlKGRvY19pZHMsIG5fZG9jcykgIyBzYW1wbGUgZG9jdW1lbnQgSURzIGZvciB0ZXN0IGRhdGEKCmRhdF90dF93b3Jkcy5jbG4uc2FtcC50cmFpbiA8LSBkYXRfdHRfd29yZHMuY2xuLnNhbXAgJT4lCiAgZmlsdGVyKGRvY19udW0gJSFpbiUgZG9jX2lkc190ZXN0KSAjIFNlbGVjdCBkb2N1bWVudHMgZm9yIHRyYWluaW5nCgpkYXRfdHRfd29yZHMuY2xuLnNhbXAudGVzdCA8LSBkYXRfdHRfd29yZHMuY2xuLnNhbXAgJT4lCiAgZmlsdGVyKGRvY19udW0gJWluJSBkb2NfaWRzX3Rlc3QpICMgU2VsZWN0IGRvY3VtZW50cyBmb3IgdGVzdAoKCmBgYAoKCiMjIERhdGEgZm9ybWF0aW5nIGZvciBTVE0gbW9kZWxzCgojIyMgUHJlLXByb2Nlc3NpbmcgZm9yIFNUTSBtb2RlbHMKClRvIGZpdCBvdXIgU1RNIG1vZGVscywgd2UnbGwgdXNlIHRoZSBmYW50YXN0aWMgYHN0bWAgcGFja2FnZS4gV2hpbGUgYHN0bWAgaGFzIGl0J3Mgb3duIGZ1bmN0aW9ucyBmb3IgcHJvY2Vzc2luZyB0ZXh0IGRhdGEsIHdlJ2xsIHRyeSB0byBkbyBtb3N0IG9mIG91ciBwcm9jZXNzaW5nIHdpdGggdGlkeXRleHQsIHdoaWNoIGFsbG93cyB1cyB0byBtYWludGFpbiBjb25zaXN0ZW50eSB3aXRoIG90aGVyIHVzZSBjYXNlcy4gCgpGaXJzdCwgd2UgbmVlZCB0byBjYXN0IG91ciB0aWR5IGZvcm1hdCB0ZXh0IGRhdGEgaW50byBhIHNvLWNhbGxlZCBgc3BhcnNlYCBkb2N1bWVudC10ZXJtIG1hdHJpeC4gV2UnbGwgYWxzbyB0YWtlIHNvbWUgb3RoZXIgcHJlLXByb2Nlc3Npbmcgc3RlcHMgdG8gc2ltcGxpZnkgdGhlIG1vZGVsaW5nIHByb2Nlc3MuIFNwZWNpZmljYWxseSwgd2UnbGwgZHJvcCB2ZXJ5IGNvbW1vbiBhbmQgdmVyeSB1bmNvbW1vbiB3b3Jkcy4gVGhpcyBjYW4gZHJhbWF0aWNhbGx5IG1pbmltaXplIHRoZSBwYXJhbWV0ZXIgc3BhY2Ugb2YgdGhlIG1vZGVsIChyZW1lbWJlciwgaXQgaGFzIGRpc3RyaWJ1dGlvbnMgb3ZlciAqZXZlcnkqIHdvcmQgZm9yICplYWNoKiB0b3BpYykgYW5kIG1pdGlnYXRlIGNoYWxsZW5nZXMgcG9zZWQgYnkgc3BhcnNpdHkuCgpgYGB7cn0KCiMgQ2FzdCB0byBzcGFyc2UgbWF0cml4LCB3aGljaCBpcyB2YWxpZCBmb3IgdGV4dG1pbmVSCnRyYWluX2R0bSA8LSBkYXRfdHRfd29yZHMuY2xuLnNhbXAudHJhaW4gJT4lCiAgY2FzdF9zcGFyc2UoZG9jX251bSwgd29yZF9sZW1tYSwgdmFsdWU9bikgCgojIFVzZXIgdGV4dG1pbm9yIGZ1bmN0aW9uIFRlcm1Eb2NGcmVxIHRvIGV4dHJhY3QgdGVybSBhbmQgZG9jdW1lbnQgZnJlcXVlbmNpZXMKCnRmIDwtIFRlcm1Eb2NGcmVxKGR0bSA9IHRyYWluX2R0bSkgJT4lCiAgICBtdXRhdGUoZG9jX3Byb3AgPSBkb2NfZnJlcS9uX2RvY3MpIAoKCiMgRXhjbHVkZSB3b3JkcyB0aGF0ICgxKSBvY2N1ciBpbiBsZXNzIHRoYW4gMSUgb2YgZG9jdW1lbnRzIG9yICgyKSBvY2N1ciBpbiBtb3JlIHRoYW4gOTklIG9mIGRvY3VtZW50cyAuCndvcmRzX3RvX2tlZXAgPC0gdGYgJT4lCiAgZmlsdGVyKGRvY19wcm9wID49IC4wMSAmIGRvY19wcm9wIDw9IC45OSkKCmNhdChwYXN0ZSgnTiB3b3JkczogJywgbnJvdyh0ZiksICdcbk4gd29yZHMgYWZ0ZXIgZmlsdGVyaW5nOiAnLCBucm93KHdvcmRzX3RvX2tlZXApLCBzZXA9JycpKQoKIyBEcm9wIHRoZXNlIHdvcmRzIGZyb20gb3VyIHRyYWluaW5nIGRhdGEKCmRhdF90dF93b3Jkcy5jbG4uc2FtcC50cmFpbiA8LSBkYXRfdHRfd29yZHMuY2xuLnNhbXAgJT4lCiAgZmlsdGVyKHdvcmRfbGVtbWEgJWluJSB3b3Jkc190b19rZWVwJHRlcm0pCgpgYGAKCkJ5IGRyb3BwaW5nIHVuY29tbW9uIGFuZCBjb21tb24gd29yZHMsIHdlJ3ZlIGRlY3JlYXNlZCBvdXIgdm9jYWJ1bGFyIGJ5IGFuIG9yZGVyIG9mIG1hZ25pdHVkZS4gSW4gcHJhY3RpY2UsIGl0J3MgaW1wb3J0YW50IHRvIGRvIHNlbnNpdGl2aXR5IGFuYWx5c2VzIG92ZXIgZGlmZmVyZW50IHRocmVzaG9sZHM7IGJ1dCBmb3Igb3VyIHB1cnBvc2VzLCB3ZSdsbCBhc3N1bWUgdGhhdCB0aGlzIHRyYW5zZm9ybWF0aW9uIGRvZXNuJ3QgZHJhbWF0aWNhbGx5IGNoYW5nZSB0aGUgbWVhbmluZyBvZiBvdXIgZG9jdW1lbnRzLiAKCk5vdyB0aGF0IHdlJ3ZlIGZpbHRlcmVkIG91dCBpbmZyZXF1ZW50L2ZyZXF1ZW50IHdvcmRzLCB3ZSdsbCByZWNhc3Qgb3VyIERUTS4gV2UnbGwgYWxzbyBjcmVhdGUgYSBkZXNpZ24gbWF0cml4IGNvbnRhaW5pbmcgb3VyIGNvdmFyaWF0ZXMuCgpgYGB7cn0KCiMgQ2FzdCB0cmFpbmluZyBkYXRhIGludG8gc3BhcnNlIERUTSAKdHJhaW5fc3BhcnNlIDwtIGRhdF90dF93b3Jkcy5jbG4uc2FtcC50cmFpbiAlPiUKICBjYXN0X3NwYXJzZShkb2NfbnVtLCB3b3JkX2xlbW1hLCB2YWx1ZT1uKQoKCiMgQ3JlYXRlIGEgZGF0ZSByYW5nZSBmcm9tIHRoZSBtaW4vbWF4IGRhdGVzIGluIG91ciB0cmFpbmluZyBkYXRhCmRhdGVfZ3JpZCA8LSB0aWJibGUoZGF0ZSA9IHNlcShtaW4oZGF0X3R0X3dvcmRzLmNsbi5zYW1wLnRyYWluJGRhdGUpLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1heChkYXRfdHRfd29yZHMuY2xuLnNhbXAudHJhaW4kZGF0ZSksIGJ5PSdkYXlzJykpICU+JQogIG11dGF0ZShkYXRlX2ludCA9IHJvd19udW1iZXIoKSkgIyBBc3NvY2lhdGUgZWFjaCBkYXRlIHdpdGggYW4gaW50ZWdlcgoKCiMgQ3JlYXRlIGRlc2lnbiBtYXRyaXggCnRyYWluX1ggPC0gZGF0X3R0X3dvcmRzLmNsbi5zYW1wLnRyYWluICU+JQogIGRpc3RpbmN0KGRvY19udW0sIFBhcnR5LCBkYXRlKSAlPiUKICBsZWZ0X2pvaW4oZGF0ZV9ncmlkKSAjIE1lcmdlIG91ciBkYXRlcyB3aXRoIHRoZSBkYXRlIGdyaWQgc28gdGhhdCB3ZSBjYW4gcmVwcmVzZW50IGRhdGUgYXMgYSBzZXF1ZW5jIG9mIGludGVnZXJzCgpgYGAKCiMgV29ya2luZyB3aXRoIFNUTSBtb2RlbHMKCiMjIEZpdHRpbmcgU1RNIG1vZGVscwoKTm93IHdlJ3JlIHJlYWR5IHRvIGZpdCBvdXIgU1RNIG1vZGVscyEgQmVjYXVzZSB3ZSBkb24ndCBrbm93IHRoZSB0cnVlIG51bWJlciBvZiB0b3BpY3MsIEssIHdlJ2xsIGZpdCBhIG1vZGVsIG92ZXIgYSBncmlkIG9mIEsgdmFsdWVzIHJhbmdpbmcgZnJvbSA1IHRvIDYwIGF0IGludGVydmFscyBvZiA1LiBTbywgaW4gdG90YWwsIHdlJ2xsIHRyYWluIDEyIHRvcGljIG1vZGVscy4gVGhpcyB0YWtlcyBxdWl0ZSBhIHdoaWxlIHRvIHJ1biwgZXZlbiBvbiBhIHBvd2VyZnVsIG1hY2hpbmUuIFRvIGhlbHAgbWluaW1pemUgdHJhaW5pbmcgdGltZSwgSSdtIHVzaW5nIHRoZSBgZnVycnJgIHBhY2thZ2UgdG8gdHJhaW4gdGhlIG1vZGVscyBpbiBwYXJhbGxlbC4gSG93ZXZlciwgZXZlbiBpbiBwYXJhbGxlbCwgdGhpcyBjb2RlIHRha2VzIGEgd2hpbGUgdG8gcnVuLiBJZiB5b3UgZG9uJ3Qgd2FudCB0byBydW4gaXQgbm93LCB5b3UgY2FuIGxvYWQgdGhlIGZpbmFsIG9iamVjdCwgYG1hbnlfbW9kZWxzYCwgZnJvbSB0aGUgYC9tb2RlbHMvYCBkaXJlY3RvcnkgaW4gb3VyIHdvcmtzaG9wIGRpcmVjdG9yeS4gCgpUbyBmaXQgb3VyIFNUTSBtb2RlbHMsIHdlJ2xsIHNwZWNpZnkgYSB2YWx1ZSBvZiBgS2AsIHRoZSBzcGFyc2UgbWF0cml4IHdlIHdhbnQgdG8gdHJhaW4gdGhlIG1vZGVsIG9uLCBvdXIgbW9kZWwgZm9yIHRvcGljIHByZXZhbGVuY2UsIGFuZCB0aGUgZGF0YS5mcmFtZSB0aGF0IGNvbnRhaW5zIG91ciBjb3ZhcmlhdGVzLiBIZXJlLCB3ZSdsbCBtb2RlbCB0b3BpYyBwcmV2ZWxhbmNlIGFzIGEgZnVuY3Rpb24gb2YgUGFydHksIGEgYmluYXJ5IGZhY3RvciwgYW5kIHRpbWUsIHdoaWNoIHdlJ2xsIG1vZGVsIHdpdGggYSBzcGxpbmUuIEluIHByYWN0aWNlLCB5b3UgbWF5IHdhbnQgdG8gdHJ5IGRpZmZlcmVudCBmdW5jdGlvbmFsIGZvcm1zLCBlLmcuIHBlcmhhcHMgZm9yIHRpbWUuIEluIHRoaXMgZGF0YSwgd2UgZG8gbm90IHJlYWxseSBoYXZlIGEgY29udGludW91cyAob3IgZXZlbiBhcHByb3hpbWF0ZWx5IGNvbnRpbnVvdXMpIG1lYXN1cmUgb2YgdGltZSwgc28gaXQgbWlnaHQgbWFrZSBtb3JlIHNlbnNlIHRvIHRyZWF0IGRheSwgb3Igd2VlaywgYXMgYSBjYXRlZ29yaWNhbCB2YXJpYWJsZS4gCgpgYGB7ciwgZXZhbD1GfQojIyBUaGlzIHRha2VzIHF1aXRlIGEgd2hpbGUgdG8gcnVuIQojIyBUbyBzYXZlIHRpbWUsIHlvdSBjYW4ganVzdCBsb2FkIHRoZSBgbWFueV9tb2RlbHNgIG9iamVjdCwgd2hpY2ggaXMgc2F2ZWQgYXMgYHN0bV9tb2RlbHMuUkRTYCBpbiB0aGUgL21vZGVsZXMgZGlyZWN0b3J5LgojIyAKCgojIFBhcmFsbGVsIG1vZGVsIGZpdHRpbmcgYWRhcHRlZCBmcm9tIGh0dHBzOi8vanVsaWFzaWxnZS5jb20vYmxvZy9ldmFsdWF0aW5nLXN0bS8KCiMgVW5jb21tZW50IGFuZCBydW4gdG8gc2V0IG51bWJlciBvZiBjb3JlcyB0byBiZSB1c2VkIGZvciBwYXJhbGxlbCBwcm9jZXNzaW5nCiMgb3B0aW9ucyhtYy5jb3JlcyA9IDYpCgojIFNldHVwIGVudiBmb3IgbXVsdGlwcm9jZXNzaW5nCnBsYW4obXVsdGlzZXNzaW9uLCBnYyA9IFRSVUUpCgptYW55X21vZGVscyA8LSBkYXRhX2ZyYW1lKEsgPSBzZXEoNSw2MCw1KSkgJT4lICMgSW5pdGlhbGl6ZSBhIGNvbHVtbiBvZiB2YWx1ZXMgZm9yIEsgCiAgbXV0YXRlKHRvcGljX21vZGVsID0gZnV0dXJlX21hcChLLCAgICMgbWFwIHZhbHVlcyBvZiBLIGludG8gYHN0bWAgaW4gcGFyYWxsZWwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB+c3RtKHRyYWluX3NwYXJzZSwgICMgU3BhcnNlIG1hdHJpeAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBLID0gLiwgICAgICAgICAjIHBsYWNlaG9sZGVyIGZvciBLCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHByZXZhbGVuY2UgPSB+IFBhcnR5KnMoZGF0ZV9pbnQpLCAjIHByZXZhbGVuY2UgbW9kZWwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGF0YSA9IHRyYWluX1gsIHZlcmJvc2U9RikpKQoKI3NhdmVSRFMobWFueV9tb2RlbHMsICcuLi9tb2RlbHMvc3RtX21vZGVscy5SRFMnKQoKYGBgCgoKYGBge3J9CiMgTG9hZCB0aGUgdHJhaW5lZCBtb2RlbHMKbWFueV9tb2RlbHMgPC0gcmVhZFJEUygnLi4vbW9kZWxzL3N0bV9tb2RlbHMuUkRTJykKYGBgCgojIyBFdmFsdWF0aW5nIFNUTSBtb2RlbHMKCk5vdyB0aGF0IHdlJ3ZlIHRyYWluZWQgb3VyIG1vZGVscywgd2UnbGwgdHJ5IHRvIHBpY2sgYSBzcGVjaWZpYyBtb2RlbCBiYXNlZCBvbiB2YXJpb3VzIG1lYXN1cmUgb2YgbW9kZWwgZml0L3F1YWxpdHkuIEluIHRoaXMgY2FzZSwgd2UncmUgdHJ5aW5nIHRvIGRlY2lkZSBvbiB0aGUgb3B0aW1hbCBudW1iZXIgb2YgVG9waWNzLiAKCioqTm90ZToqKiBJbiBwcmFjdGljZSwgdW5sZXNzIHRoZXJlIGlzIGEgdmVyeSBjbGVhciB3aW5uZXIsIGl0J3MgcHJvYmFibHkgYSBnb29kIGlkZWEgdG8gY29uZHVjdCBzZW5zaXRpdml0eSBhbmFseXNlcyBvdmVyIG1vZGVscyB3aXRoIGRpZmZlcmVudCBudW1iZXJzIG9mIHRvcGljcy4gCgpGaXJzdCwgd2UnbGwgZXh0cmFjdCBhIGJ1bmNoIG9mIG1vZGVsIGZpdCBtZXRyaWNzOgoKYGBge3J9CgojIEFkYXB0ZWQgZnJvbSBodHRwczovL2p1bGlhc2lsZ2UuY29tL2Jsb2cvZXZhbHVhdGluZy1zdG0vCgpoZWxkb3V0IDwtIG1ha2UuaGVsZG91dCh0cmFpbl9zcGFyc2UpICMgSGVyZSB3ZSdyZSBzZXR0aW5nIGFzaWRlIHNvbWUgaGVsZG91dCBkYXRhIHRoYXQgd2UnbGwgdXNlIHRvIGV2YWx1YXRlIG91ciBtb2RlbC4gCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBIb3dldmVyLCByZWFsbHksIHRoaXMgZGF0YSBzaG91bGQgTk9UIGJlIHRha2VuIGZyb20gb3VyIHRyYWluaW5nIGRhdGEhCgoKa19yZXN1bHQgPC0gbWFueV9tb2RlbHMgJT4lCiAgbXV0YXRlKGV4Y2x1c2l2aXR5ID0gbWFwKHRvcGljX21vZGVsLCBleGNsdXNpdml0eSksCiAgICAgICAgIHNlbWFudGljX2NvaGVyZW5jZSA9IG1hcCh0b3BpY19tb2RlbCwgc2VtYW50aWNDb2hlcmVuY2UsIHRyYWluX3NwYXJzZSksCiAgICAgICAgIGV2YWxfaGVsZG91dCA9IG1hcCh0b3BpY19tb2RlbCwgZXZhbC5oZWxkb3V0LCBoZWxkb3V0JG1pc3NpbmcpLAogICAgICAgICByZXNpZHVhbCA9IG1hcCh0b3BpY19tb2RlbCwgY2hlY2tSZXNpZHVhbHMsIHRyYWluX3NwYXJzZSksCiAgICAgICAgIGJvdW5kID0gIG1hcF9kYmwodG9waWNfbW9kZWwsIGZ1bmN0aW9uKHgpIG1heCh4JGNvbnZlcmdlbmNlJGJvdW5kKSksCiAgICAgICAgIGxmYWN0ID0gbWFwX2RibCh0b3BpY19tb2RlbCwgZnVuY3Rpb24oeCkgbGZhY3RvcmlhbCh4JHNldHRpbmdzJGRpbSRLKSksCiAgICAgICAgIGxib3VuZCA9IGJvdW5kICsgbGZhY3QsCiAgICAgICAgIGl0ZXJhdGlvbnMgPSBtYXBfZGJsKHRvcGljX21vZGVsLCBmdW5jdGlvbih4KSBsZW5ndGgoeCRjb252ZXJnZW5jZSRib3VuZCkpKQoKYGBgCgpOb3csIGxldCdzIHBsb3Qgc29tZSBvZiB0aGVzZSBtZXRyaWNzIGFzIGEgZnVuY3Rpb24gb2YgSzoKCmBgYHtyfQprX3Jlc3VsdCAlPiUKICB0cmFuc211dGUoSywKICAgICAgICAgICAgYExvd2VyIGJvdW5kYCA9IGxib3VuZCwKICAgICAgICAgICAgUmVzaWR1YWxzID0gbWFwX2RibChyZXNpZHVhbCwgImRpc3BlcnNpb24iKSwKICAgICAgICAgICAgYFNlbWFudGljIGNvaGVyZW5jZWAgPSBtYXBfZGJsKHNlbWFudGljX2NvaGVyZW5jZSwgbWVhbiksCiAgICAgICAgICAgIGBIZWxkLW91dCBsaWtlbGlob29kYCA9IG1hcF9kYmwoZXZhbF9oZWxkb3V0LCAiZXhwZWN0ZWQuaGVsZG91dCIpKSAlPiUKICBnYXRoZXIoTWV0cmljLCBWYWx1ZSwgLUspICU+JQogIGdncGxvdChhZXMoSywgVmFsdWUsIGNvbG9yID0gTWV0cmljKSkgKwogIGdlb21fbGluZShzaXplID0gMS41LCBhbHBoYSA9IDAuNywgc2hvdy5sZWdlbmQgPSBGQUxTRSkgKwogIGZhY2V0X3dyYXAofk1ldHJpYywgc2NhbGVzID0gImZyZWVfeSIpICsKICBsYWJzKHggPSAiSyAobnVtYmVyIG9mIHRvcGljcykiLAogICAgICAgeSA9IE5VTEwsCiAgICAgICB0aXRsZSA9ICJNb2RlbCBkaWFnbm9zdGljcyBieSBudW1iZXIgb2YgdG9waWNzIiwKICAgICAgIHN1YnRpdGxlID0gIlRoZXNlIGRpYWdub3N0aWNzIGluZGljYXRlIHRoYXQgYSBnb29kIG51bWJlciBvZiB0b3BpY3Mgd291bGQgYmUgYXJvdW5kIDUwIG9yIDYwIikKYGBgCgpVbGltYXRlbHksIHdlIHdhbnQgdG8gcGljayBhIG1vZGVsIHRoYXQgbWF4aW1pemVzIHNlbWFudGljIGNvaGVyZW5jZSwgcm91Z2hseSB0aGUgbGlrZWxpaG9vZCB0aGF0IGhpZ2ggcHJvYmFiaWxpdHkgd29yZHMgaW4gYSBnaXZlbiB0b3BpYyBjby1vY2N1ciBpbiBhIGhpZ2gtcHJvYmFiaWxpdHkgZG9jdW1lbnQgZm9yIHRoYXQgdG9waWMuIEhvd2V2ZXIsIGF0IHRoZSBzYW1lIHRpbWUsIHdlIHdhbnQgdG8gbWluaW1pemUgb3VyIHJlc2lkdWFscyBhbmQgbWF4aW1pemUgdGhlIGhlbGQtb3V0IGxpa2VsaWhvb2QgYW5kIHRoZSBtYXJnaW5hbCBwcm9iYWJpbGl0eSBvZiB0aGUgZGF0YSBnaXZlbiB0aGUgbW9kZWwsIHdoaWNoIGlzIHJlZmVycmVkIHRvLCBoZXJlLCBhcyB0aGUgImxvd2VyIGJvdW5kIi4gVW5mb3J0dW5hdGVseSwgc2VtYW50aWMgY29oZXJlbmNlIGFuZCB0aGVzZSBvdGhlciBtZXRyaWNzIHVzdWFsbHkgbW92ZSBpbiBvcHBvc2l0ZSBkaXJlY3Rpb25zLiAKCkluIHRoaXMgY2FzZSwgYmFzZWQgb24gb3VyIHJlc2lkdWFscywgaGVsZC1vdXQgbGlrZWxpaG9vZCwgYW5kIGxvd2VyIGJvdW5kIHBsb3RzLCA1MCA8PSBLID49IDYwIGlzIGEgZ29vZCByYW5nZS4gSXQgYWxzbyBsb29rcyBsaWtlIHRoZXNlIG1vZGVscyBhcmUgdGllZCAoYXQgdGhlIGxvd2VzdCkgbGV2ZWxzIG9mIHNlbWFudGljIGNvaGVyZW5jZS4gCgoKIyMjIENvaGVyZW5jZSB2cyBFeGNsdXNpdml0eQoKCkFub3RoZXIgaW1wb3J0YW50IGRpYWdub3N0aWMgaXMgKmV4Y2x1c2l2aXR5Kiwgd2hpY2ggcmVwcmVzZW50cyB0aGUgZGVncmVlIHRvIHdoaWNoIHRoZSBoaWdoZXN0IHByb2JhYmlsaXR5IHdvcmRzIGluIGEgdG9waWMgYXJlICpleGNsdXNpdmUqIG9yICp1bmlxdWUqIHRvIHRoYXQgdG9waWMuIFRoaXMgaXMgYSB2YWx1YWJsZSBjb21wbGVtZW50IHRvIGNvaGVyZW5jZSwgYmVjYXVzZSB5b3UgY291bGQgbWF4aW1pemUgY29oZXJlbmNlIGJ5IGFzc2lnbmluZyB0aGUgd29yZHMgd2l0aCB0aGUgaGlnaGVzdCBtYXJnaW5hbCBlbXBpcmljYWwgcHJvYmFiaWxpdGllcyB0byBhbGwgdG9waWNzIChlLmcuIGFsbCB0b3BpY3MgcGxhY2UgdGhlIG1vc3QgZGVuc2l0eSBvbiAidGhlIiwgImFuZCIsIGFuZCAiaXMiLCBmb3IgZXhhbXBsZSkuIEluIHRoaXMgY2FzZSwgd2UgY291bGQgdGVsbCB0aGF0IHRoaXMgaXMgYSAiYmFkIiBtb2RlbCBieSBsb29raW5nIGF0IHRoZSBleGNsdXNpdml0eSBzY29yZXMgZm9yIHRoZSBtb2RlbCdzIHRvcGljcywgd2hpY2ggd291bGQgYmUgdmVyeSBsb3cgYmVjYXVzZSBhbGwgdG9waWNzIHNoYXJlIHRoZWlyIGhpZ2ggcHJvYmFiaWxpdHkgd29yZHMuIAoKCmBgYHtyfQoKS19tZWFucyA8LSBrX3Jlc3VsdCAlPiUKICBzZWxlY3QoSywgZXhjbHVzaXZpdHksIHNlbWFudGljX2NvaGVyZW5jZSkgJT4lCiAgZmlsdGVyKEsgJWluJSBjKDQwLCA0NSwgNTAsIDU1LCA2MCkpICU+JQogIHVubmVzdChjb2xzID0gYyhleGNsdXNpdml0eSwgc2VtYW50aWNfY29oZXJlbmNlKSkgJT4lCiAgbXV0YXRlKEsgPSBhcy5mYWN0b3IoSykpICU+JQogIGdyb3VwX2J5KEspICU+JQogIHN1bW1hcml6ZV9hbGwoLmZ1bnMgPSBjKG1lYW4sIHNkKSkKCgoKa19yZXN1bHQgJT4lCiAgc2VsZWN0KEssIGV4Y2x1c2l2aXR5LCBzZW1hbnRpY19jb2hlcmVuY2UpICU+JQogIGZpbHRlcihLICVpbiUgYyg0MCwgNDUsIDUwLCA1NSwgNjApKSAlPiUKICB1bm5lc3QoY29scyA9IGMoZXhjbHVzaXZpdHksIHNlbWFudGljX2NvaGVyZW5jZSkpICU+JQogIG11dGF0ZShLID0gYXMuZmFjdG9yKEspKSAlPiUKICBnZ3Bsb3QoYWVzKHNlbWFudGljX2NvaGVyZW5jZSwgZXhjbHVzaXZpdHksIGNvbG9yID0gSykpICsKICBnZW9tX3BvaW50KHNpemUgPSAyLCBhbHBoYSA9IDAuNykgKwogIGxhYnMoeCA9ICJTZW1hbnRpYyBjb2hlcmVuY2UiLAogICAgICAgeSA9ICJFeGNsdXNpdml0eSIsCiAgICAgICB0aXRsZSA9ICJDb21wYXJpbmcgZXhjbHVzaXZpdHkgYW5kIHNlbWFudGljIGNvaGVyZW5jZSIsCiAgICAgICBzdWJ0aXRsZSA9ICJNb2RlbHMgd2l0aCBmZXdlciB0b3BpY3MgaGF2ZSBoaWdoZXIgc2VtYW50aWMgY29oZXJlbmNlIGZvciBtb3JlIHRvcGljcywgYnV0IGxvd2VyIGV4Y2x1c2l2aXR5IikgKwogICNmYWNldF93cmFwKEt+LikgKwogIGdlb21faGxpbmUoZGF0YT1LX21lYW5zLCBhZXMoeWludGVyY2VwdD1leGNsdXNpdml0eV9mbjEsIGNvbG9yPUspKSArIAogIGdlb21fdmxpbmUoZGF0YT1LX21lYW5zLCBhZXMoeGludGVyY2VwdD1zZW1hbnRpY19jb2hlcmVuY2VfZm4xLCBjb2xvcj1LKSkgCiAgCgpgYGAKCkl0IGxvb2tzIGxpa2UgdGhlIG1vZGVsIHdpdGggNjAgdG9waWNzIGhhcyB0aGUgaGlnaGVzdCBhdmVyYWdlIGV4Y2x1c2l2aXR5IGFuZCB0aGUgM3JkIGhpZ2hlc3QgYXZlcmFnZSBjb2hlcmVuY2UuIEhvd2V2ZXIsIHRoaXMgaXMgYSBiaXQgaGFyZCB0byBzZWUsIHNvIHdlIGNhbiBhbHNvIGxvb2sgYXQgdGhlIHBvaW50IGVzdGltYXRlcy4KCgpgYGB7cn0KS19tZWFucyAlPiUKICByZW5hbWUobWVhbl9leGNsdXNpdml0eSA9IGV4Y2x1c2l2aXR5X2ZuMSwKICAgICAgICAgbWVhbl9zZW1hbnRpY19jb2hlcmVuY2UgPSBzZW1hbnRpY19jb2hlcmVuY2VfZm4xLAogICAgICAgICBzZF9leGNsdXNpdml0eSA9IGV4Y2x1c2l2aXR5X2ZuMiwKICAgICAgICAgc2Rfc2VtYW50aWNfY29oZXJlbmNlID0gc2VtYW50aWNfY29oZXJlbmNlX2ZuMikgJT4lCiAgc2VsZWN0KEssIG1lYW5fZXhjbHVzaXZpdHksIHNkX2V4Y2x1c2l2aXR5LCBtZWFuX3NlbWFudGljX2NvaGVyZW5jZSwgc2Rfc2VtYW50aWNfY29oZXJlbmNlKSAlPiUKICBtdXRhdGVfaWYoaXMubnVtZXJpYywgcm91bmQsIGRpZ2l0cz0yKQpgYGAKCgpJbnRlcmVzdGluZ2x5LCBpdCBsb29rcyBsaWtlIGV4Y2x1c2l2aXR5IGlzIHRoZSBzYW1lIGZvciBLID0gNTAsIDU1LCBhbmQgNjAsIHRob3VnaCB0aGUgU0QgaXMgYSBsaXR0bGUgbG93ZXIgZm9yIEsgPSA1NSBhbmQgNjAuIEluIGNvbnRyYXN0LCB0aGUgbW9kZWwgd2l0aCBLID0gNjAgYWN0dWFsbHkgaGFzIHRoZSA0dGggbG93ZXN0IHNlbWFudGljIGNvaGVyZW5jZS4gCgojIyMgQ2hvb3NpbmcgYSBtb2RlbAoKVWx0aW1hdGVseSwgb3VyIG1vZGVsIHJlc2lkdWFscyBhbmQgbWFyZ2luYWwgZml0IHN0YXRpc3RpY3MgaW5kaWNhdGUgdGhhdCB0aGUgbW9kZWwgd2l0aCA2MCB0b3BpY3MgaXMgdGhlIGJlc3QuIEhvd2V2ZXIsIGNvbXBhcmlzb25zIHNlbWFudGljIGNvaGVyZW5jZSBhbmQgZXhjbHVzaXZpdHkgc3VnZ2VzdCB0aGF0IG90aGVyIG1vZGVscyAqY291bGQqIGJlIGp1c3QgYXMgZ29vZCwgZGVwZW5kaW5nIG9uIGhvdyB5b3UgZGVmaW5lIG1vZGVsIHN1Y2Nlc3MuIAoKV2hlbiBpdCdzIGhhcmQgdG8gaWRlbnRpZnkgYSBjbGVhciB3aW5uZXIsIHlvdSBzaG91bGQgYWxtb3N0IGFsd2F5cyBjb25kdWN0IHNlbnNpdGl2aXR5IGFuYWx5c2VzIGFjcm9zcyBtdWx0aXBsZSBtb2RlbHMhIElmIHRoZXkgYWxsIGxlYWQgeW91IHRvIHRoZSBzYW1lIGNvbmNsdXNpb24sIHRoZW4gcGVyaGFwcyB0aGF0IGNvbmNsdXNpb24gd2FycmFudHMgZ3JlYXRlciB0cnVzdC4gSG93ZXZlciwgaWYgdGhleSBhbGwgbGVhZCB5b3UgdG8gZGlmZmVyZW50IGNvbmNsdXNpb25zLCB0aGVuIHlvdSBwcm9iYWJseSBzaG91bGRuJ3QgdHJ1c3QgYW55IG9mIHRoZW0hCgpIb3dldmVyLCBmb3Igb3VyIHB1cnBvc2VzLCB3ZSdsbCBjaG9vc2UgdGhlIG1vZGVsIHdpdGggSz02MCBhbmQgcHJvY2VlZCB3aXRoIG91ciBhbmFseXNlcy4gCgoKYGBge3J9CnN0bV9tb2RlbC4xLnRyYWluIDwtIGtfcmVzdWx0ICU+JSAKICBmaWx0ZXIoSyA9PSA2MCkgJT4lIAogIHB1bGwodG9waWNfbW9kZWwpICU+JSAKICAuW1sxXV0KCnN0bV9tb2RlbC4xLnRyYWluCmBgYAoKIyBFeHBsb3JpbmcgU1RNIG1vZGVscwoKTm93IHRoYXQgd2UndmUgc2VsZWN0ZWQgYSB0b3BpYyBtb2RlbCwgd2UgY2FuIGJlZ2luIHRvIGFuc3dlciBzb21lIG9mIG91ciBxdWVzdGlvbnMuIAoKCiMjIFdoYXQgYHRvcGljc2AgYXJlIHJlbGV2YW50IHRvIG91ciBjb3JwdXM/CgpUbyB0YWtlIGEgaGlnaC1sZXZlbCBnbGFuY2UgYXQgdGhlIHRvcGljcyBlc3RpbWF0ZWQgYnkgb3VyIG1vZGVsLCB3ZSBjYW4gdXNlIHRoZSBgc3RtYCBgcGxvdGAgZnVuY3Rpb24gd2l0aCBgdHlwZT0nc3VtbWFyeWAuIFRoaXMgcGxvdCBvcmRlcnMgdG9waWNzIGJ5IHRoZSBtYXJnaW5hbCBwcm9wb3J0aW9uIChlLmcuIGxpa2VsaWhvb2Qgb2Ygb2NjdXJhbmNlKSBhbmQgc2hvd3MgdGhlIHRvcCB3b3JkcyBhc3NvY2lhdGVkIHdpdGggZWFjaCB0b3BpYy4gCgpgYGB7ciwgZmlnLmhlaWdodD0xNX0KcGxvdChzdG1fbW9kZWwuMS50cmFpbiwgdHlwZSA9ICJzdW1tYXJ5IiwgeGxpbSA9IGMoMCwgLjMpLCB0ZXh0LmNleD0xLjUpCgpgYGAKCkJ5IGRlZmF1bHQsIHRoZSAqdG9wKiB3b3JkcyBhcmUgZGVmaW5lZCBhcyB0aGUgd29yZHMgd2l0aCB0aGUgaGlnaGVzdCBwcm9iYWJpbGl0eS4gSG93ZXZlciwgd2UgY2FuIGFsc28gY2hhbmdlIHRoaXMgc28gdGhhdCB0aGUgKnRvcCogd29yZHMgYXJlIHRoZSB3b3JkcyB3aXRoIHRoZSBoaWdoZXN0IGV4Y2x1c2l2aXR5IHNjb3JlLgoKCmBgYHtyLCBmaWcuaGVpZ2h0PTE1fQoKcGxvdChzdG1fbW9kZWwuMS50cmFpbiwgdHlwZSA9ICJzdW1tYXJ5IiwgeGxpbSA9IGMoMCwgLjMpLCB0ZXh0LmNleD0xLjUsIG49NSwgbGFiZWx0eXBlPSdmcmV4JykKCmBgYAoKCjxkaXYgY2xhc3M9ImFsZXJ0IGFsZXJ0LXN1Y2Nlc3MiIHJvbGU9ImFsZXJ0Ij4KICA8c3Ryb25nPlF1ZXN0aW9uOjwvc3Ryb25nPiBCYXNlZCBvbiB0aGVzZSBwbG90cywgd2hpY2ggdG9waWMocykgYXJlIG1vc3QgcmVsZXZhbnQgdG8gaW1wZWFjaG1lbnQ/CjwvZGl2PgoKCgojIyBJbnRlci10b3BpYyBjb3JyZWxhdGlvbnMgCgpJbiBjb250cmFzdCB0byAqdmFuaWxsYSogTERBIG1vZGVscywgU1RNIG1vZGVscyBlc3RpbWF0ZSBhIGNvdmFyaWFuY2UgbWF0cml4IGZvciB0aGUgZGlzdHJpYnV0aW9uIG9mIHRvcGljcy4gV2UgY2FuIHVzZSB0aGlzIGNvdmFyaWFuY2UgbWF0cml4IHRvIHZpc3VhbGl6ZSBhc3NvY2lhdGlvbnMgYW1vbmcgdG9waWNzLiAKCmBgYHtyLCBmaWcuaGVpZ2h0PTgsIGZpZy53aWR0aD04fQpsaWJyYXJ5KGlncmFwaCkKY29ybWF0IDwtIHRvcGljQ29ycihzdG1fbW9kZWwuMS50cmFpbikKc2V0LnNlZWQoMTIzKQpwbG90KGNvcm1hdCwgbml0ZXI9NTAwMCwgcmVwdWxzZXJhZD02MF40KjEwLCAKICAgICBlZGdlLmFycm93LnNpemU9MC41LCAKICAgICB2ZXJ0ZXgubGFiZWwuY2V4PTAuNzUsIAogICAgIHZlcnRleC5sYWJlbC5mYW1pbHk9IkhlbHZldGljYSIsCiAgICAgdmVydGV4LmxhYmVsLmZvbnQ9MiwKICAgICB2ZXJ0ZXguc2hhcGU9ImNpcmNsZSIsIAogICAgIHZlcnRleC5zaXplPTIsIAogICAgIHZlcnRleC5sYWJlbC5jb2xvcj0iYmxhY2siLCAKICAgICBlZGdlLndpZHRoPTAuNSkKYGBgCgpUaGlzIGlzIGhhcmQgdG8gcmVhZCwgc28gbGV0J3MgdHJ5IGEgZGlmZmVyZW50IGtpbmQgb2YgcGxvdDogCgpgYGB7cn0KCmxpYnJhcnkocmVzaGFwZTIpCgptZWx0ZWRfY29ybWF0IDwtIG1lbHQoY29ybWF0JGNvcikKCmdncGxvdChkYXRhID0gbWVsdGVkX2Nvcm1hdCwgYWVzKHg9VmFyMSwgeT1WYXIyLCBmaWxsPXZhbHVlKSkgKyAKZ2VvbV90aWxlKCkKCmBgYAoKVGhpcyBpcyBzdGlsbCBoYXJkIHRvIHJlYWQhIEJlY2F1c2UgSSdtIHByaW1hcmlseSBpbnRlcmVzdGVkIGluIGFzc29jaWF0aW9ucyB3aXRoIFRvcGljIDI1LCBJJ2xsIGp1c3QgcGxvdCB0aGUgY29ycmVsYXRpb25zIHdpdGggdGhhdCB0b3BpYy4KCmBgYHtyfQpsaWJyYXJ5KHBsb3RseSkgCgpwIDwtIG1lbHRlZF9jb3JtYXQgJT4lCiAgZmlsdGVyKFZhcjEgPT0gMjUgJiBWYXIyICE9IDI1KSAlPiUKICBnZ3Bsb3QoYWVzKHggPSBWYXIyLCB5ID0gdmFsdWUpKSArIGdlb21fdGV4dChhZXMobGFiZWw9VmFyMikpICsgCiAgeWxhYignQ29ycmVsYXRpb24nKSArCiAgeGxhYignVG9waWMnKSArCiAgZ2d0aXRsZSgnQ29ycmVsYXRpb25zIG9mIFRvcGljIDI1IHdpdGggb3RoZXIgdG9waWNzJykKICAKZ2dwbG90bHkocCkKYGBgCgpJdCBsb29rcyBsaWtlIHRvcGljIDI1IGlzIG1vc3Qgc3Ryb25nbHkgcmVsYXRlZCB0byAzNSwgNTcsIDUzLCBzbyBsZXQncyB0YWtlIGEgY2xvc2VyIGxvb2sgYXQgdGhvc2UgdG9waWNzLiAKCiMjIEV2YWx1YXRpbmcgYHRvcGljYCBjb250ZW50CgpPdXQgb2YgNjAgdG9waWNzLCB0aGUgb25lJ3MgdGhhdCBzZWVtIG1vc3QgcmVsZXZhbnQgdG8gb3VyIHF1ZXN0aW9ucyBhYm91dCBpbXBlYWNobWVudCBhcmUgMjUsIDM1LCA1NywgYW5kIDUzLiBCdXQsIHdoYXQgZG8gdGhlc2UgdG9waWNzICptZWFuKj8gVG8gZ2V0IGEgYmV0dGVyIGlkZWEgb2YgdGhlaXIgc3ViamVjdGl2ZSBtZWFuaW5nLCB3ZSBjYW4gbG9vayBhZ2FpbiBhdCB0aGVpciB0b3Agd29yZHMuIAoKCmBgYHtyLCBmaWcud2lkdGg9OCwgZmlnLmhlaWdodD00fQoKcGxvdChzdG1fbW9kZWwuMS50cmFpbiwgdHlwZSA9ICJzdW1tYXJ5IiwgeGxpbSA9IGMoMCwgLjMpLCBuPTUsIGxhYmVsdHlwZT0ncHJvYicsIAogICAgIHRvcGljcyA9IGMoMjUsIDM1LCA1NywgNTMpKQoKYGBgCgoKYGBge3IsIGZpZy53aWR0aD04LCBmaWcuaGVpZ2h0PTR9CgpwbG90KHN0bV9tb2RlbC4xLnRyYWluLCB0eXBlID0gInN1bW1hcnkiLCB4bGltID0gYygwLCAuMyksIG49NSwgbGFiZWx0eXBlPSdmcmV4JywgCiAgICAgdG9waWNzID0gYygyNSwgMzUsIDU3LCA1MykpCgpgYGAKCgoKPGRpdiBjbGFzcz0iYWxlcnQgYWxlcnQtc3VjY2VzcyIgcm9sZT0iYWxlcnQiPgogIDxzdHJvbmc+UXVlc3Rpb246PC9zdHJvbmc+IEJhc2VkIG9uIHRoZXNlIHBsb3RzLCB3aGF0IGRvIHRoZXNlIHRvcGljcyBtZWFuPwo8L2Rpdj4KCgojIyMgRXhhbWluaW5nIHJlbGV2YW50IGRvY3VtZW50cyAKCkxvb2tpbiBhdCB0aGUgdG9wIHdvcmRzIGFzc29jaWF0ZWQgd2l0aCBhIHRvcGljIGlzIGEgZ29vZCB3YXkgdG8gZ2V0IGFuIGlkZWEgb2Ygd2hhdCB0aGUgdG9waWMgcmVwcmVzZW50cy4gSG93ZXZlciwgdGhlcmUgaXMgYW5vdGhlciBjcnVjaWFsIHNvdXJjZSBvZiBpbmZvcm1hdGlvbjogdGhlICpkb2N1bWVudHMqIG1vc3Qgc3Ryb25nbHkgYXNzb2NpYXRlZCB3aXRoIHRoZSB0b3BpYy4gV2hlbiB0cnlpbmcgdG8gc3VtbWFyaXplIHRvcGljcywgeW91IHNob3VsZCAqYWx3YXlzKiBsb29rIGF0IHRoZSB0b3Agd29yZHMgKmFuZCogdGhlIHRvcCBkb2N1bWVudHMuCgpXZSBjYW4gZG8gdGhpcyB1c2luZyB0aGUgYGZpbmRUaG91Z2h0c2AgZnVuY3Rpb24sIHdoaWNoIHByaW50cyB0aGUgdGV4dCBvZiB0aGUgZG9jdW1lbnRzIG1vc3Qgc3Ryb25nbHkgYXNzb2NpYXRlZCB3aXRoIGEgcGFydGljdWxhciB0b3BpYy4gSG93ZXZlciwgdG8gZG8gdGhpcywgd2UgZmlyc3QgbmVlZCB0byBtYWtlIG91ciB0ZXh0cyBhY2Nlc3NpYmxlLiBGb3Igc2ltcGxpY2l0eSwgSSdsbCBqdXN0IGV4YW1pbmUgdGhlIGZpcnN0IDUwMCBjaGFyYWN0ZXJzIGluIGVhY2ggcmVsZXZhbnQgZG9jdW1lbnQuCgoKCmBgYHtyfQoKdGV4dHMgPSBkYXRfdHRfd29yZHMgJT4lICMgVGhpcyB0aWR5IGRhdGEuZnJhbWUgY29udGFpbnMgdGhlIG9yaWdpbmFsIHdvcmRzIChpLmUuIGJlZm9yZSB3ZSBsZW1tYXRpemVkKQogIGZpbHRlcihkb2NfbnVtICVpbiUgdW5pcXVlKGRhdF90dF93b3Jkcy5jbG4uc2FtcC50cmFpbiRkb2NfbnVtKSkgJT4lICMgS2VlcCBvbmx5IHRoZSBkb2NzIGluIG91ciB0cmFpbmluZyBkYXRhCiAgZ3JvdXBfYnkoZG9jX251bSkgJT4lIAogIHN1bW1hcml6ZSh0ZXh0ID0gc3RyX3N1YihwYXN0ZTAod29yZCwgY29sbGFwc2U9JyAnKSwgMSw1MDApKSAlPiUgIyBDb2xsYXBzZSB0aGUgcm93cyBvZiB3b3JkcyBpbnRvIGEgc2luZ2xlIGNlbGwKICB1bmdyb3VwKCkKCmZpbmRUaG91Z2h0cyhzdG1fbW9kZWwuMS50cmFpbiwgdGV4dHMgPSB0ZXh0cyR0ZXh0LCB0b3BpY3MgPSBjKDI1KSwgbj0zKQoKYGBgCgoKCmBgYHtyfQoKZmluZFRob3VnaHRzKHN0bV9tb2RlbC4xLnRyYWluLCB0ZXh0cyA9IHRleHRzJHRleHQsIHRvcGljcyA9IGMoMzUpLCBuPTMpCgpgYGAKCgpgYGB7cn0KZmluZFRob3VnaHRzKHN0bV9tb2RlbC4xLnRyYWluLCB0ZXh0cyA9IHRleHRzJHRleHQsIHRvcGljcyA9IGMoNTcpLCBuPTMpCgpgYGAKCgoKYGBge3J9CmZpbmRUaG91Z2h0cyhzdG1fbW9kZWwuMS50cmFpbiwgdGV4dHMgPSB0ZXh0cyR0ZXh0LCB0b3BpY3MgPSBjKDUzKSwgbj0zKQoKYGBgCgoKCjxkaXYgY2xhc3M9ImFsZXJ0IGFsZXJ0LXN1Y2Nlc3MiIHJvbGU9ImFsZXJ0Ij4KICA8c3Ryb25nPlF1ZXN0aW9uOjwvc3Ryb25nPiBCYXNlZCBvbiB0aGVzZSBleGNlcnB0cywgYXMgd2VsbCBhcyB0aGUgdG9waWNzJyB0b3Agd29yZHMsIGhvdyB3b3VsZCB5b3UgbGFiZWwgdGhlIHRvcGljcz8KPC9kaXY+CgoqIFRvcGljIDI1OgoqIFRvcGljIDM1OiAKKiBUb3BpYyA1NzogCiogVG9waWMgNTM6CgoKIyMgSHlwb3RoZXNpcyB0ZXN0aW5nIHdpdGggU1RNcwoKQ2xlYXJseSwgVG9waWMgMjUgaXMgdGhlIG1vc3Qgc3Ryb25nbHkgcmVsYXRlZCB0byBpbXBlYWNobWVudC4gU28sIGxldCdzIHVzZSB0aGUgdG9waWMgcHJldmFsZW5jZSBjb21wb25lbnQgb2Ygb3VyIG1vZGVsIHRvIGVzdGltYXRlIHRoZSBkaXN0cmlidXRpb24gb2YgVG9waWMgMjUgb3ZlciB0aW1lIGFuZCBieSBwYXJ0eS4gCgpUbyBkbyB0aGlzLCB3ZSdsbCB1c2UgYHN0bWAncyBgZXN0aW1hdGVFZmZlY3RgIGZ1bmN0aW9uLiBUaGVuLCB3ZSdsbCB1c2UgYHRpZHlzdG1gJ3MgZnVuY3Rpb24gYGV4dHJhY3QuZXN0aW1hdGVFZmZlY3RgIHRvIGV4dHJhY3QgYSB0aWR5IGRhdGFmcmFtZSBvZiB0aGUgZXN0aW1hdGVkIGVmZmVjdHMuCgpgYGB7cn0KbGlicmFyeSh0aWR5c3RtKQoKcHJlcCA9IGVzdGltYXRlRWZmZWN0KGMoMjUpIH4gUGFydHkqcyhkYXRlX2ludCksIHN0bV9tb2RlbC4xLnRyYWluLCBtZXRhPXRyYWluX1gpCgogIAplZmZzIDwtIHB1cnJyOjptYXAoYygnRGVtb2NyYXRpYycsICdSZXB1YmxpY2FuJyksICMgTGV2ZWxzIG9mIG1vZGVyYXRvcgogICAgICAgICAgICAgICAgICAgfmV4dHJhY3QuZXN0aW1hdGVFZmZlY3QocHJlcCwgIyBlZmZlY3RzIGVzdGltYXRlIG9iamVjdAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgImRhdGVfaW50IiwgIyBUaGUgSVYgd2Ugd2FudCB0byBsb29rIGF0CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtb2RlbCA9IHN0bV9tb2RlbC4xLnRyYWluLCAjIE91ciBTVE0gbW9kZWwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1vZGVyYXRvcj0nUGFydHknLCAjIE1vZGVyYXRvciAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1vZGVyYXRvci52YWx1ZSA9IC4pKSAlPiUgIyBNb2RlcmF0b3IgbGV2ZWxzLCB3aGljaCB3ZSBzcGVjaWZ5IHZpYSBgbWFwYAogIGRvLmNhbGwoJ3JiaW5kJywgLikgIyBIZXJlLCB3ZSByYmluZCB0aGUgbWFwcGVkIGxpc3RzLCB3aGljaCB5aWVsZHMgYSBzaW5nbGUgREYKCmhlYWQoZWZmcykKCmBgYAoKSW1wb3J0YW50bHksIHRoZSBgZXN0aW1hdGVFZmZlY3RgIGZ1bmN0aW9uIHVzZXMgKmRvY3VtZW50cyogYXMgdW5pdHMgYW5kIHRoZSB0b3BpYyBwcm9wb3J0aW9uIGFzIHRoZSBvdXRjb21lLiBUaHVzLCBvdXIgZWZmZWN0cyBlc3RpbWF0ZXMgcmVmbGVjdCBleHBlY3RlZCBjaGFuZ2VzIGluIHRoZSB0b3BpYyBwcm9wb3J0aW9uIGZvciBhIGdpdmVuIGRvY3VtZW50LCBjb25kaXRpb25hbCBvbiBvdXIgY292YXJpYXRlcy4gCgpUbyBnZXQgYSBiZXR0ZXIgaWRlYSBvZiB3aGF0IHRoZXNlIGVmZmVjdHMgaW1wbHksIGxldCdzIHZpc3VhbGl6ZSB0aGVtLgoKCmBgYHtyfQoKCmVmZnMgJT4lCiAgbGVmdF9qb2luKGRhdGVfZ3JpZCwgYnkgPSBjKCdjb3ZhcmlhdGUudmFsdWUnPSdkYXRlX2ludCcpKSAlPiUKICBnZ3Bsb3QoYWVzKHggPSBkYXRlLCB5ID0gZXN0aW1hdGUpKSArIAogIGdlb21fcmliYm9uKGFlcyh5bWluPWNpLmxvd2VyLCB5bWF4PWNpLnVwcGVyLCBmaWxsPW1vZGVyYXRvci52YWx1ZSksIGFscGhhPS4yNSkgKyAKICBnZW9tX2xpbmUoYWVzKGNvbG9yPW1vZGVyYXRvci52YWx1ZSkpICsKICB0aGVtZV9hcGEoKSArIAogIHlsYWIoJ1RvcGljIFByb3BvcnRpb24nKSArCiAgeGxhYignRGF0ZScpICsKICBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQ9YXMubnVtZXJpYyhhc19kYXRldGltZSgnMTk5OC0xMC0wOCcpKSwgbGluZXR5cGU9MikgKyAKICBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQ9YXMubnVtZXJpYyhhc19kYXRldGltZSgnMTk5OC0xMi0xOScpKSwgbGluZXR5cGU9MikgKwogIGdlb21fbGFiZWwoYWVzKHggPSBhc19kYXRldGltZSgnMTk5OC0xMC0wOCcpLCB5PS4zMiwgbGFiZWwgPSAiSW1wZWFjaG1lbnQgSW5pdGlhdGVkIikpICsKICBnZW9tX2xhYmVsKGFlcyh4ID0gYXNfZGF0ZXRpbWUoJzE5OTgtMTItMTknKSwgeT0uMzIsIGxhYmVsID0gIkltcGVhY2htZW50IFZvdGUiKSkgKyAKICBnZ3RpdGxlKCdFc3RpbWF0ZWQgdG9waWMgcHJvcG9ydGlvbnMgYnkgZGF0ZSBhbmQgcGFydHknKSArIAogIGZhY2V0X3dyYXAodG9waWN+LiwgbmNvbD0xKQoKYGBgCgoKCjxkaXYgY2xhc3M9ImFsZXJ0IGFsZXJ0LXN1Y2Nlc3MiIHJvbGU9ImFsZXJ0Ij4KICA8c3Ryb25nPlF1ZXN0aW9uOjwvc3Ryb25nPiBXaGF0IGRvZXMgdGhpcyBmaWd1cmUgc3VnZ2VzdD8KPC9kaXY+CgoKTm93LCBsZXQncyBsb29rIHNwZWNpZmljYWxseSBhdCB0aGUgZWZmZWN0cyBmb3IgdGhlIHR3byBkYXlzIHJlbGV2YW50IHRvIGltcGVhY2htZW50LCB0aGUgZGF5IHRoZSBpbXBlYWNobWVudCB3YXMgaW5pdGlhdGVkIGFuZCB0aGUgZGF5IGl0IHdhcyB2b3RlZCBvbi4gCgpgYGB7cn0KCnByZXBfbWFyZyA9IGVzdGltYXRlRWZmZWN0KGMoMjUpIH4gUGFydHkqcyhkYXRlX2ludCksIHN0bV9tb2RlbC4xLnRyYWluLCBtZXRhPXRyYWluX1gpCgoKZGF0ZV9pbnRzIDwtIGRhdGVfZ3JpZCAlPiUKICBmaWx0ZXIoZGF0ZSA9PSBhc19kYXRldGltZSgnMTk5OC0xMC0wOCcpIHwgZGF0ZSA9PSBhc19kYXRldGltZSgnMTk5OC0xMi0xOScpKSAlPiUKICBwdWxsKGRhdGVfaW50KQogIApwYXJ0eV9lZmZzIDwtIHB1cnJyOjptYXAoZGF0ZV9pbnRzLCAjIExldmVscyBvZiBtb2RlcmF0b3IKICAgICAgICAgICAgICAgICAgIH5leHRyYWN0LmVzdGltYXRlRWZmZWN0KHByZXAsICMgZWZmZWN0cyBlc3RpbWF0ZSBvYmplY3QKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJQYXJ0eSIsICMgVGhlIElWIHdlIHdhbnQgdG8gbG9vayBhdAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbW9kZWwgPSBzdG1fbW9kZWwuMS50cmFpbiwgIyBPdXIgU1RNIG1vZGVsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtb2RlcmF0b3I9J2RhdGVfaW50JywgIyBNb2RlcmF0b3IgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtb2RlcmF0b3IudmFsdWUgPSAuKSkgJT4lICMgTW9kZXJhdG9yIGxldmVscywgd2hpY2ggd2Ugc3BlY2lmeSB2aWEgYG1hcGAKICBkby5jYWxsKCdyYmluZCcsIC4pICMgSGVyZSwgd2UgcmJpbmQgdGhlIG1hcHBlZCBsaXN0cywgd2hpY2ggeWllbGRzIGEgc2luZ2xlIERGCgoKCnBhcnR5X2VmZnMgJT4lCiAgbGVmdF9qb2luKGRhdGVfZ3JpZCwgYnkgPSBjKCdtb2RlcmF0b3IudmFsdWUnPSdkYXRlX2ludCcpKSAlPiUKICBnZ3Bsb3QoYWVzKHkgPSBlc3RpbWF0ZSwgeCA9IGFzLmZhY3RvcihkYXRlKSwgY29sb3I9Y292YXJpYXRlLnZhbHVlKSkgKyAKICBnZW9tX3BvaW50KHNpemU9MiwgcG9zaXRpb249cG9zaXRpb25fZG9kZ2Uod2lkdGg9LjI1KSkgKwogIGdlb21fZXJyb3JiYXIoYWVzKHltaW49Y2kubG93ZXIsIHltYXg9Y2kudXBwZXIpLCB3aWR0aD0uMSwgcG9zaXRpb249cG9zaXRpb25fZG9kZ2Uod2lkdGg9LjI1KSkgKwogIHRoZW1lX2FwYSgpICsKICB5bGFiKCdUb3BpYyBwcm9wb3J0aW9uJykgKwogIHhsYWIoJ1BhcnR5JykgKwogIGdndGl0bGUoJ0VmZmVjdCBvZiBQYXJ0eSBvbiBUb3BpYyAyNSBieSBkYXRlJykKCgpgYGAKCgoKCjxkaXYgY2xhc3M9ImFsZXJ0IGFsZXJ0LXN1Y2Nlc3MiIHJvbGU9ImFsZXJ0Ij4KICA8c3Ryb25nPlF1ZXN0aW9uOjwvc3Ryb25nPiBXaGF0IGRvZXMgdGhpcyBmaWd1cmUgc3VnZ2VzdD8KPC9kaXY+CgoKIyMgSW52ZXN0aWdhdGluZyBUb3BpYyBDb250ZW50CgpJdCBzZWVtcyBjbGVhciB0aGF0IERlbW9jcmF0cycgZmxvb3Igc3BlZWNoZXMgd2VyZSBtb3JlIHJlbGV2YW50IHRvIFRvcGljIDI1IHRoYW4gUmVwdWJsaWNhbnMuIAoKSG93ZXZlciwgd2hhdCBpZiB0aGV5IHNwZWFrIGFib3V0IHRoZXNlIHRvcGljcyBpbiBkaWZmZXJlbnQgd2F5cz8gVG8gaW52ZXN0aWdhdGUgdGhpcyBoeXBvdGhlc2lzLCB3ZSBjYW4gZXN0aW1hdGUgYSBuZXcgbW9kZWwgdGhhdCBtb2RlbHMgdG9waWMgY29udGVudCBhcyBhIGZ1bmN0aW9uIG9mIFBhcnR5LiAgIAoKCmBgYHtyLCBldmFsPUZ9CgpzdG1fbW9kZWwuMi50cmFpbiA8LSBzdG0odHJhaW5fc3BhcnNlLAogICAgICAgICAgICBLID0gNjAsCiAgICAgICAgICAgIHByZXZhbGVuY2UgPSB+IFBhcnR5KnMoZGF0ZV9pbnQpLCAjIHByZXZhbGVuY2UgbW9kZWwKICAgICAgICAgICAgY29udGVudCA9IH4gUGFydHksCiAgICAgICAgICAgIGRhdGEgPSB0cmFpbl9YLAogICAgICAgICAgICB2ZXJib3NlPUYpCgpzYXZlUkRTKHN0bV9tb2RlbC4yLnRyYWluLCAnLi4vbW9kZWxzL3N0bV9tb2RlbF8yX3RyYWluLlJEUycpCgpgYGAKCmBgYHtyfQpzdG1fbW9kZWwuMi50cmFpbiA8LSByZWFkUkRTKCcuLi9tb2RlbHMvc3RtX21vZGVsXzJfdHJhaW4uUkRTJykKYGBgCgpOb3csIHdlJ2xsIHZpc3VhbGl6ZSBiZXR3ZWVuLXBhcnR5IGRpZmZlcmVuY2VzIGluIHRvcGljICpjb250ZW50KiB1c2luZyB0aGUgYHN0bWAgcGxvdCBmdW5jdGlvbiB3aXRoIGB0eXBlPSdwZXJzcGVjdGl2ZXMnYC4KCgpgYGB7ciwgZmlnLmhlaWdodD04LCBmaWcud2lkdGg9MTB9CnBsb3Qoc3RtX21vZGVsLjIudHJhaW4sIHR5cGUgPSAicGVyc3BlY3RpdmVzIiwgdG9waWNzID0gMjUsIG4gPSAxMDApCmBgYAoKSW4gdGhpcyBmaWd1cmUsIGEgd29yZCdzIHNpemUgaW5kaWNhdGVzIGl0cyBhc3NvY2lhdGlvbiB3aXRoIHRoZSB0b3BpYy4gRnVydGhlciwgaXQncyBwb3NpdGlvbiBvbiB0aGUgWC1heGlzIGluZGljYXRlcyBpdCdzIGRpZmZlcmVudGlhbCBhc3NvY2lhdGlvbiB3aXRoIHRoZSBzcGVjaWZpZWQgbGV2ZWxzIG9mIHRoZSBjb3ZhcmlhdGUuIAoKCjxkaXYgY2xhc3M9ImFsZXJ0IGFsZXJ0LXN1Y2Nlc3MiIHJvbGU9ImFsZXJ0Ij4KICA8c3Ryb25nPlF1ZXN0aW9uOjwvc3Ryb25nPiBXaGF0IGRvZXMgdGhpcyBmaWd1cmUgc3VnZ2VzdD8KPC9kaXY+CgoKIyBWYWxpZGF0aW9uIHdpdGggU1RNIG1vZGVscyAKCkF0IHRoaXMgcG9pbnQsIHdlJ3ZlIGZpdCBhIGJ1bmNoIG9mIFNUTSBtb2RlbHMgYW5kIHBpY2tlZCBvbmUgZm9yIGZ1cnRoZXIgZXhwbG9yYXRpb24uIEJhc2VkIG9uIHdoYXQgd2Ugb2JzZXJ2ZWQsIGl0IHNlZW1zIHRoYXQgIERlbW9jcmF0cyBzcG9rZSBtb3JlIGFib3V0IGltcGVhY2htZW50IG9uIHRoZSBkYXkgb2YgdGhlIHZvdGUsIGJ1dCBhbHNvIHRoYXQgdGhlaXIgZGlzY3Vzc2lvbnMgb2YgaW1wZWFjaG1lbnQgZm9jdXNlZCBtb3JlIG9uICJwcm9jZXNzIiwgd2hlcmVhcyBSZXB1YmxpY2FucycgZGlzY3Vzc2lvbnMgb2YgaW1wZWFjaG1lbnQgZm9jdXNlZCBtb3JlIGV4cGxpY2l0bHkgb24gdGhlIHByZXNpZGVudCBhbmQgd29yZHMgbGlrZSAianVzdGljZSIsICJ0cnV0aCIsIGFuZCAibGllIi4gCgpHaXZlbiBvdXIgdW5kZXJzdGFuZGluZyBvZiB0aGUgZGF0YSwgdGhpcyBtYWtlcyBzZW5zZSEgSG93ZXZlciwgYXJlIHRoZXNlIGZpbmRpbmdzIHJvYnVzdD8gVG8gYWRkcmVzcyB0aGlzIHF1ZXN0aW9uLCB3ZSB3aWxsIGZpdCBhIG5ldyBtb2RlbCBvbiBvdXIgaGVsZC1vdXQgY29uZmlybWF0aW9uIGRhdGEuIElkZWFsbHksIHdlJ2QgbGlrZSB0byBzZWUgdGhlIHNhbWUgdG9waWMgc3RydWN0dXJlIGFuZCBjb21lIHRvIHRoZSBzYW1lIGNvbmNsdXNpb25zLiBIb3dldmVyLCBpZiBvdXIgY29uY2x1c2lvbnMgZGV2aWF0ZSBmcm9tIG91ciBjdXJyZW50IGV4cGVjdGF0aW9ucywgd2UgbWF5IG5lZWQgdG8gYWNjZXB0IHRoZSBwb3NzaWJpbGl0eSB0aGF0IG91ciBjb25jbHVzaW9ucyBhcmUgbm90IHJlbGlhYmxlLiAKCgoKYGBge3J9CgojIENhc3QgdHJhaW5pbmcgZGF0YSBpbnRvIHNwYXJzZSBEVE0gCnRlc3Rfc3BhcnNlIDwtIGRhdF90dF93b3Jkcy5jbG4uc2FtcC50ZXN0ICU+JQogIGNhc3Rfc3BhcnNlKGRvY19udW0sIHdvcmRfbGVtbWEsIHZhbHVlPW4pCgoKIyBDcmVhdGUgYSBkYXRlIHJhbmdlIGZyb20gdGhlIG1pbi9tYXggZGF0ZXMgaW4gb3VyIHRyYWluaW5nIGRhdGEKZGF0ZV9ncmlkIDwtIHRpYmJsZShkYXRlID0gc2VxKG1pbihkYXRfdHRfd29yZHMuY2xuLnNhbXAudGVzdCRkYXRlKSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtYXgoZGF0X3R0X3dvcmRzLmNsbi5zYW1wLnRlc3QkZGF0ZSksIGJ5PSdkYXlzJykpICU+JQogIG11dGF0ZShkYXRlX2ludCA9IHJvd19udW1iZXIoKSkgIyBBc3NvY2lhdGUgZWFjaCBkYXRlIHdpdGggYW4gaW50ZWdlcgoKCiMgQ3JlYXRlIGRlc2lnbiBtYXRyaXggCnRlc3RfWCA8LSBkYXRfdHRfd29yZHMuY2xuLnNhbXAudGVzdCAlPiUKICBkaXN0aW5jdChkb2NfbnVtLCBQYXJ0eSwgZGF0ZSkgJT4lCiAgbGVmdF9qb2luKGRhdGVfZ3JpZCkgIyBNZXJnZSBvdXIgZGF0ZXMgd2l0aCB0aGUgZGF0ZSBncmlkIHNvIHRoYXQgd2UgY2FuIHJlcHJlc2VudCBkYXRlIGFzIGEgc2VxdWVuYyBvZiBpbnRlZ2VycwoKYGBgCgoKYGBge3J9CgpzdG1fbW9kZWwuMi50ZXN0IDwtIHN0bSh0ZXN0X3NwYXJzZSwKICAgICAgICAgICAgSyA9IDYwLAogICAgICAgICAgICBwcmV2YWxlbmNlID0gfiBQYXJ0eSpzKGRhdGVfaW50KSwgIyBwcmV2YWxlbmNlIG1vZGVsCiAgICAgICAgICAgIGNvbnRlbnQgPSB+IFBhcnR5LAogICAgICAgICAgICBkYXRhID0gdGVzdF9YLAogICAgICAgICAgICB2ZXJib3NlPUYpCgpzYXZlUkRTKHN0bV9tb2RlbC4yLnRlc3QsICcuLi9tb2RlbHMvc3RtX21vZGVsX3Rlc3QuUkRTJykKCmBgYAoKCkZpcnN0LCBsZXQncyBsb29rIGF0IHRoZSB0b3Agd29yZHMgZm9yIGVhY2ggdG9waWMuIAoKCmBgYHtyLCBmaWcuaGVpZ2h0PTE1fQoKcGxvdChzdG1fbW9kZWwuMi50ZXN0LCB0eXBlID0gInN1bW1hcnkiLCB4bGltID0gYygwLCAuMyksIHRleHQuY2V4PTEuNSwgbj01KQoKYGBgCgpVaCBvaCwgb3VyIG5pY2UgImltcGVhY2giIHRvcGljIGRpZG4ndCBzaG93IHVwISAKCgo8ZGl2IGNsYXNzPSJhbGVydCBhbGVydC1zdWNjZXNzIiByb2xlPSJhbGVydCI+CiAgPHN0cm9uZz5RdWVzdGlvbjo8L3N0cm9uZz4gQXJlIHRoZXJlIGFueSB0b3BpY3MgcmVsYXRlZCB0byBvdXIgcXVlc3Rpb25zIG9mIGludGVyZXN0Pwo8L2Rpdj4KCgojIyBFeHRyYWN0aW5nIHRoZSBgYmV0YWAgbWF0cml4IAoKT25lIHdheSB0byBsb29rIGZvciByZWxldmFudCB0b3BpY3MgaXMgdG8gaWRlbnRpZnkgdG9waWNzIHRoYXQgcGxhY2UgdGhlIGhpZ2hlc3QgcHJvYmFiaWxpdHkgb24gcmVsZXZhbnQga2V5d29yZHMsIHN1Y2ggYXMgImltcGVhY2htZW50Ii4gVG8gZG8gdGhpcywgd2UnbGwgZXh0cmFjdCB0aGUgYGJldGFgIG1hdHJpeCBmcm9tIG91ciBtb2RlbCBhbmQgaWRlbnRpZnkgdGhlIHRvcGljcyB0aGF0IHBsYWNlIHRoZSBoaWdoZXN0IHByb2JhYmlsaXR5IG9uICJpbXBlYWNobWVudCIKCmBgYHtyfQp0ZF9iZXRhIDwtIHRpZHkoc3RtX21vZGVsLjIudGVzdCwgbWF0cml4PSdiZXRhJykgJT4lICMgV2UgY2FuIGV4dHJhY3QgYSB0aWR5IGRhdGFmcmFtZSBjb250YWluaW5nIHRoZSBiZXRhIG1hdHJpeCBmcm9tIG91ciBtb2RlbAogIGRyb3BfbmEodG9waWMpICMgRm9yIHNvbWUgcmVhc29uLCB0aGVyZSBhcmUgc29tZSBOQSByb3dzIGZvciB0b3BpY3MvdGVybXMuIFNob3VsZCBsb29rIGludG8gdGhpcyEKCnRkX2JldGEgJT4lCiAgZmlsdGVyKHRlcm0gPT0gJ2ltcGVhY2htZW50JykgJT4lICMgUGljayBhIHJlbGV2YW50IHdvcmQKICBhcnJhbmdlKGRlc2MoYmV0YSkpICU+JSAjIEFycmFuZ2UgYnkgaGlnaGVzdCBwcm9iYWJpbGl0eQogIG11dGF0ZShiZXRhID0gcm91bmQoYmV0YSwgZGlnaXRzPTIpKSAlPiUKICBoZWFkKCkKICAKYGBgCgpJbnRlcmVzdGluZywgaXQgbG9va3MgbGlrZSB0b3BpYyA1MCBoYXMsIGJ5IGZhciwgdGhlIGdyZWF0ZXN0IHByb2JhYmlsaXR5IGRlbnNpdHkgb3ZlciAnaW1wZWFjaG1lbnQnLiBMZXQncyBsb29rIGF0IHRoaXMgdG9waWMsIGFsb25nIHdpdGggMTUsIDQ2LCBhbmQgMzUKCgpgYGB7ciwgZmlnLmhlaWdodD01fQoKcGxvdChzdG1fbW9kZWwuMi50ZXN0LCB0eXBlID0gInN1bW1hcnkiLCB4bGltID0gYygwLCAuMyksIG49NSwgdG9waWNzPWMoNTAsMTUsNDYsMzUpKQoKYGBgCgoKCjxkaXYgY2xhc3M9ImFsZXJ0IGFsZXJ0LXN1Y2Nlc3MiIHJvbGU9ImFsZXJ0Ij4KICA8c3Ryb25nPlF1ZXN0aW9uOjwvc3Ryb25nPiBBcmUgdGhlc2UgdG9waWNzIHJlbGF0ZWQgdG8gaW1wZWFjaG1lbnQ/IElmIHNvLCBpbiB3aGF0IHdheT8KPC9kaXY+CgoKT2theSwgaWYgeW91IHNxdWludCwgVG9waWMgNTAgY291bGQgZGVmaW5pdGVseSBiZSByZWxhdGVkIHRvIGltcGVhY2htZW50LiBBbHNvLCAiUGF1bGEiIGluIHRvcGljIDQ2IGlzIHByb2JhYmx5IHJlbGF0ZWQgdG8gUGF1bGEgSm9uZXMsIHRoZSBwZXJzb24gd2hvIGZpbGVkIGEgc2V4dWFsIGhhcmFzc21lbnQgbGF3c3VpdCBmaWxlZCBhZ2FpbnN0IENsaW50b24uIAoKIyMgRXhhbWluaW5nIHJlbGV2YW50IGRvY3VtZW50cyAKCkxldCdzIGRpZyBhIGJpdCBkZWVwZXIgYnkgbG9va2luZyBhdCB0aGUgcmVsZXZhbnQgZG9jdW1lbnRzIGZvciBlYWNoIHRvcGljOgoKYGBge3J9Cgp0ZXh0cyA9IGRhdF90dF93b3JkcyAlPiUgIyBUaGlzIHRpZHkgZGF0YS5mcmFtZSBjb250YWlucyB0aGUgb3JpZ2luYWwgd29yZHMgKGkuZS4gYmVmb3JlIHdlIGxlbW1hdGl6ZWQpCiAgZmlsdGVyKGRvY19udW0gJWluJSB1bmlxdWUoZGF0X3R0X3dvcmRzLmNsbi5zYW1wLnRlc3QkZG9jX251bSkpICU+JSAjIEtlZXAgb25seSB0aGUgZG9jcyBpbiBvdXIgdHJhaW5pbmcgZGF0YQogIGdyb3VwX2J5KGRvY19udW0pICU+JSAKICBzdW1tYXJpemUodGV4dCA9IHN0cl9zdWIocGFzdGUwKHdvcmQsIGNvbGxhcHNlPScgJyksIDEsNTAwKSkgJT4lICMgQ29sbGFwc2UgdGhlIHJvd3Mgb2Ygd29yZHMgaW50byBhIHNpbmdsZSBjZWxsCiAgdW5ncm91cCgpCgpmaW5kVGhvdWdodHMoc3RtX21vZGVsLjIudGVzdCwgdGV4dHMgPSB0ZXh0cyR0ZXh0LCB0b3BpY3MgPSBjKDUwKSwgbj0zKQoKYGBgCgpUaGlzIHRvcGljIGNlcnRhaW5seSBzZWVtcyByZWxldmFudCB0byBpbXBlYWNobWVudDsgdGhlc2UgKHZlcnkgZmV3ISkgZG9jdW1lbnRzIGFsc28gc3VnZ2VzdCB0aGF0IG1heWJlIHRoaXMgdG9waWMgaXMgYXNzb2NpYXRlZCB3aXRoICpub3QqIHN1cHBvcnRpbmcgaW1wZWFjaG1lbnQuCgoKYGBge3J9CgpmaW5kVGhvdWdodHMoc3RtX21vZGVsLjIudGVzdCwgdGV4dHMgPSB0ZXh0cyR0ZXh0LCB0b3BpY3MgPSBjKDM1KSwgbj0zKQoKYGBgCgpUaGlzIHRvcGljIGFsc28gc2VlbXMgdG8gYmUgYWJvdXQgaW1wZWFjaG1lbnQ7IGhvd2V2ZXIsIGl0IHNlZW1zIHRoYXQgdGhlIHRvcCBkb2N1bWVudHMgZXhwcmVzcyBwb3NpdGlvbnMgKmZvciogaW1wZWFjaG1lbnQuIAoKCmBgYHtyfQpmaW5kVGhvdWdodHMoc3RtX21vZGVsLjIudGVzdCwgdGV4dHMgPSB0ZXh0cyR0ZXh0LCB0b3BpY3MgPSBjKDQ2KSwgbj0zKQoKYGBgCgpUaGlzIGFsc28gYXBwZWFycyB0byBiZSBhYm91dCBpbXBlYWNobWVudCwgdGhvdWdoIHdpdGggZ3JlYXRlciBmb2N1cyBvbiBseWluZyBhbmQgcHJvY2VlZGluZ3MuCgoKCmBgYHtyfQpmaW5kVGhvdWdodHMoc3RtX21vZGVsLjIudGVzdCwgdGV4dHMgPSB0ZXh0cyR0ZXh0LCB0b3BpY3MgPSBjKDE1KSwgbj0zKQoKYGBgCgpUaGlzIGFwcGVhcnMgdG8gYmUgbGVzcyByZWxldmFudCB0byBpbXBlYWNobWVudCAoYXMgd2UgaGF2ZSBiZWVuIHRoaW5raW5nIGFib3V0IGl0KSBhbmQgbW9yZSByZWxldmFudCB0byBwcm9jZWVkaW5ncy4gCgoKCjxkaXYgY2xhc3M9ImFsZXJ0IGFsZXJ0LXN1Y2Nlc3MiIHJvbGU9ImFsZXJ0Ij4KICA8c3Ryb25nPlF1ZXN0aW9uOjwvc3Ryb25nPiBXaGF0IGRvIHRoZXNlIHRvcGljcyBzZWVtIHRvIGJlIGFib3V0Pwo8L2Rpdj4KCiogMzU6IAoqIDQ2OgoqIDM1OiAKKiAxNTogCgojIyBIeXBvdGhlc2lzIHRlc3Rpbmcgb24gdmFsaWRhdGlvbiBzZXQKCldoaWxlIHRoZXNlIHRvcGljcyBhcmUgZGlmZmVyZW50IGZyb20gd2hhdCB3ZSBvYnNlcnZlZCBpbiBvdXIgdHJhaW5pbmcgZGF0YSwgdGhleSBzdGlsbCBzZWVtIHJlbGV2YW50LiBMZXQncyBnbyBhaGVhZCBhbmQgdmlzdWFsaXplIHRoZSBlZmZlY3RzIG9mIG91ciBjb3ZhcmlhdGVzOiAKCmBgYHtyfQpsaWJyYXJ5KHRpZHlzdG0pCgpwcmVwLnRlc3QgPSBlc3RpbWF0ZUVmZmVjdChjKDUwLCA0NiwgMzUpIH4gUGFydHkqcyhkYXRlX2ludCksIHN0bV9tb2RlbC4yLnRlc3QsIG1ldGE9dGVzdF9YKQoKICAKZWZmcy50ZXN0IDwtIHB1cnJyOjptYXAoYygnRGVtb2NyYXRpYycsICdSZXB1YmxpY2FuJyksICMgTGV2ZWxzIG9mIG1vZGVyYXRvcgogICAgICAgICAgICAgICAgICAgfmV4dHJhY3QuZXN0aW1hdGVFZmZlY3QocHJlcC50ZXN0LCAjIGVmZmVjdHMgZXN0aW1hdGUgb2JqZWN0CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiZGF0ZV9pbnQiLCAjIFRoZSBJViB3ZSB3YW50IHRvIGxvb2sgYXQKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1vZGVsID0gc3RtX21vZGVsLjIudGVzdCwgIyBPdXIgU1RNIG1vZGVsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtb2RlcmF0b3I9J1BhcnR5JywgIyBNb2RlcmF0b3IgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtb2RlcmF0b3IudmFsdWUgPSAuKSkgJT4lICMgTW9kZXJhdG9yIGxldmVscywgd2hpY2ggd2Ugc3BlY2lmeSB2aWEgYG1hcGAKICBkby5jYWxsKCdyYmluZCcsIC4pICMgSGVyZSwgd2UgcmJpbmQgdGhlIG1hcHBlZCBsaXN0cywgd2hpY2ggeWllbGRzIGEgc2luZ2xlIERGCgpgYGAKCgpgYGB7ciwgZmlnLmhlaWdodD02fQoKbGFiZWxfZGF0IDwtIGRhdGEuZnJhbWUoZGF0ZSA9IGFzLm51bWVyaWMoYXNfZGF0ZXRpbWUoJzE5OTgtMTAtMDgnKSksIGxhYmVsPSd0ZXN0JywgZXN0aW1hdGU9LjIpCiAgICAgICAgICAgICAgICAgICAgICAgIAplZmZzLnRlc3QgJT4lCiAgbGVmdF9qb2luKGRhdGVfZ3JpZCwgYnkgPSBjKCdjb3ZhcmlhdGUudmFsdWUnPSdkYXRlX2ludCcpKSAlPiUKICBtdXRhdGUodG9waWMgPSByZWNvZGUodG9waWMsIGA1MGAgPSAnT3Bwb3NlIGltcGVhY2htZW50JywgYDQ2YCA9ICdMeWluZycsIGAzNWAgPSAnU3VwcG9ydCBpbXBlYWNobWVudCcgKSkgJT4lCiAgZ2dwbG90KGFlcyh4ID0gZGF0ZSwgeSA9IGVzdGltYXRlKSkgKyAKICBnZW9tX3JpYmJvbihhZXMoeW1pbj1jaS5sb3dlciwgeW1heD1jaS51cHBlciwgZmlsbD1tb2RlcmF0b3IudmFsdWUpLCBhbHBoYT0uMjUpICsgCiAgZ2VvbV9saW5lKGFlcyhjb2xvcj1tb2RlcmF0b3IudmFsdWUpKSArCiAgdGhlbWVfYXBhKCkgKyAKICB5bGFiKCdUb3BpYyBQcm9wb3J0aW9uJykgKwogIHhsYWIoJ0RhdGUnKSArCiAgZ2VvbV92bGluZSh4aW50ZXJjZXB0PWFzLm51bWVyaWMoYXNfZGF0ZXRpbWUoJzE5OTgtMTAtMDgnKSksIGxpbmV0eXBlPTIpICsgCiAgZ2VvbV92bGluZSh4aW50ZXJjZXB0PWFzLm51bWVyaWMoYXNfZGF0ZXRpbWUoJzE5OTgtMTItMTknKSksIGxpbmV0eXBlPTIpICsKICBnZW9tX2xhYmVsKGFlcyh4ID0gYXNfZGF0ZXRpbWUoJzE5OTgtMTAtMDgnKSwgeT0uMzIsIGxhYmVsID0gIkltcGVhY2htZW50IEluaXRpYXRlZCIpKSArCiAgZ2VvbV9sYWJlbChhZXMoeCA9IGFzX2RhdGV0aW1lKCcxOTk4LTEyLTE5JyksIHk9LjMyLCBsYWJlbCA9ICJJbXBlYWNobWVudCBWb3RlIikpICsgCiAgZ2d0aXRsZSgnRXN0aW1hdGVkIHRvcGljIHByb3BvcnRpb25zIGJ5IGRhdGUgYW5kIHBhcnR5JykgKyAKICBmYWNldF93cmFwKHRvcGljfi4sIG5jb2w9MSkKCmBgYAoKCgo8ZGl2IGNsYXNzPSJhbGVydCBhbGVydC1zdWNjZXNzIiByb2xlPSJhbGVydCI+CiAgPHN0cm9uZz5RdWVzdGlvbjo8L3N0cm9uZz4gV2hhdCBkb2VzIHRoaXMgZmlndXJlIHN1Z2dlc3Q/CjwvZGl2PgoKCiMjIEludmVzdGlnYXRpbmcgVG9waWMgQ29udGVudCBvbiB2YWxpZGF0aW9uIHNldAoKRmluYWxseSwgd2UgY2FuIHRha2UgYSBsb29rIGF0IHBhcnR5IGRpZmZlcmVuY2VzIGluIHRvcGljIGNvbnRlbnQuIEZpcnN0LCBsZXQncyBsb29rIGF0IFRvcGljIDUwLCB0aGUgdG9waWMgd2UncmUgdGhpbmtpbmcgb2YgYXMgYXNzb2NpYXRlZCB3aXRoIG9wcG9zaXRpb24gdG8gaW1wZWFjaG1lbnQuIAoKYGBge3IsIGZpZy5oZWlnaHQ9OCwgZmlnLndpZHRoPTEwfQpwbG90KHN0bV9tb2RlbC4yLnRlc3QsIHR5cGUgPSAicGVyc3BlY3RpdmVzIiwgdG9waWNzID0gNTAsIG4gPSAxMDApCmBgYAoKVGhpcyBsb29rcyBzb21ld2hhdCBzaW1pbGFyIHRvIHRoZSBwZXJzcGVjdGl2ZXMgcGxvdCBvZiBUb3BpYyAyNSBpbiBvdXIgdHJhaW5pbmcgZGF0YS4gSG93ZXZlciwgdGhlcmUgYXJlIHNvbWUga2V5IGRpZmZlcmVuY2VzLCB0b28uCgoKTm93IGxldCdzIGxvb2sgYXQgVG9waWMgMzUsIHRoZSB0b3BpYyB3ZSdyZSB0aGlua2luZyBvZiBhcyBhc3NvY2lhdGVkIHdpdGggc3VwcG9ydGluZyBpbXBlYWNobWVudDoKCmBgYHtyLCBmaWcuaGVpZ2h0PTgsIGZpZy53aWR0aD0xMH0KcGxvdChzdG1fbW9kZWwuMi50ZXN0LCB0eXBlID0gInBlcnNwZWN0aXZlcyIsIHRvcGljcyA9IDM1LCBuID0gMTAwKQpgYGAKCkludGVyZXN0aW5nLCBpdCBsb29rcyBsaWtlLCBhdCBsZWFzdCBzb21ldGltZXMsIERlbW9jcmF0cyBtaWdodCBiZSB0YWxraW5nIGFib3V0IG90aGVyIGlzc3VlcyAoZS5nLiBub3QgQ2xpbnRvbidzIGltcGVhY2htZW50KSBpbiB0aGUgY29udGV4dCBvZiB0aGlzIHRvcGljLiAKCgpGaW5hbGx5LCBsZXQncyBsb29rIGF0IFRvcGljIDQ2LCB0aGUgdG9waWMgdGhhdCBzZWVtcyB0byBiZSBhIGxpdHRsZSBtb3JlIGdlbmVyYWxseSBhYm91dCAibHlpbmciLgoKYGBge3IsIGZpZy5oZWlnaHQ9OCwgZmlnLndpZHRoPTEwfQpwbG90KHN0bV9tb2RlbC4yLnRlc3QsIHR5cGUgPSAicGVyc3BlY3RpdmVzIiwgdG9waWNzID0gNDYsIG4gPSAxMDApCmBgYAoKSGVyZSwgd2UgY2FuIHNlZSB0aGF0IFJlcHVibGljYW4ncyBhcmUgbW9yZSBsaWtlbHkgdG8gbWVudGlvbiBMZXdpbnNreSBhbmQgSm9uZXMsIGtleSBwZW9wbGUgaW4gdGhlIGNhc2UgKmFnYWluc3QqIENsaW50b24uIFRoZWlyIHVzZSBvZiB0aGlzIHRvcGljIGFsc28gcGxhY2VzIG1vcmUgZGVuc2l0eSBvbiB3b3JkcyBsaWtlICJsaWUiIGFuZCAicGVyanVyeSIuCgoKIyBDb25jbHVzaW9ucyAKCgo8ZGl2IGNsYXNzPSJhbGVydCBhbGVydC1zdWNjZXNzIiByb2xlPSJhbGVydCI+CiAgPHN0cm9uZz5RdWVzdGlvbjo8L3N0cm9uZz4gQmFzZWQgb24gdGhlc2UgYW5hbHlzZXMsIHdoYXQgY29uY2x1c2lvbnMgd291bGQgeW91IGRyYXc/CjwvZGl2PgoK